diff --git a/multi_pms_properties/README.rst b/multi_pms_properties/README.rst new file mode 100644 index 0000000000..9071c6003f --- /dev/null +++ b/multi_pms_properties/README.rst @@ -0,0 +1,100 @@ +==================== +multi_pms_properties +==================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:63aa2d9bf2f7be1cb90c028270bb6d8850f7492fb066ed649f409279bb9ccdaf + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fpms-lightgray.png?logo=github + :target: https://github.com/OCA/pms/tree/14.0/multi_pms_properties + :alt: OCA/pms +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/pms-14-0/pms-14-0-multi_pms_properties + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/pms&target_branch=14.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Technical addon to support multiproperty in property management system (PMS). + +**Table of contents** + +.. contents:: + :local: + +Installation +============ + +To install this module, you only need to add it to your addons, and load it as +a server-wide module. + +This can be done with the ``server_wide_modules`` parameter in ``/etc/odoo.conf`` +or with the ``--load`` command-line parameter + +``server_wide_modules = "multi_pms_properties"`` + +Usage +===== + +* Use the standard multicompany guidelines applied to pms.property: + + ``_check_pms_properties_auto like model attribute to autocheck on create/write`` + ``check_pms_properties like field attribute to check relational record properties consistence`` + ``This module not implement propety dependent fields`` + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Commit [Sun] + +Contributors +~~~~~~~~~~~~ + +* `Commit [Sun] `: + + * Dario Lodeiros + * Eric Antones + * Sara Lago + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/pms `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/multi_pms_properties/__init__.py b/multi_pms_properties/__init__.py new file mode 100644 index 0000000000..16fb7d2d44 --- /dev/null +++ b/multi_pms_properties/__init__.py @@ -0,0 +1,64 @@ +# Copyright 2021 Dario Lodeiros +# Copyright 2021 Eric Antones +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging + +from odoo import fields +from odoo.tools import config + +from . import models + + +def _description_domain(self, env): + if self.check_company and not self.domain: + if self.company_dependent: + if self.comodel_name == "res.users": + # user needs access to current company (self.env.company) + return "[('company_ids', 'in', allowed_company_ids[0])]" + else: + return "[('company_id', 'in', [allowed_company_ids[0], False])]" + else: + # when using check_company=True on a field on 'res.company', the + # company_id comes from the id of the current record + cid = "id" if self.model_name == "res.company" else "company_id" + if self.comodel_name == "res.users": + # User allowed company ids = user.company_ids + return f"['|', (not {cid}, '=', True), ('company_ids', 'in', [{cid}])]" + else: + return f"[('company_id', 'in', [{cid}, False])]" + + if self.check_pms_properties and not self.domain: + record = env[self.model_name] + # Skip company_id domain to avoid domain multiproperty error in inherited views + if ( + self.check_pms_properties + and not self.domain + and self.name not in ["company_id"] + ): + if self.model_name == "pms.property": + prop1 = "id" + prop2 = f"[{prop1}]" + elif "pms_property_id" in record._fields: + prop1 = "pms_property_id" + prop2 = f"[{prop1}]" + else: + prop1 = prop2 = "pms_property_ids" + coprop = ( + "pms_property_id" + if "pms_property_id" in env[self.comodel_name]._fields + else "pms_property_ids" + ) + return f"['|', '|', \ + (not {prop1}, '=', True), \ + ('{coprop}', 'in', {prop2}), \ + ('{coprop}', '=', False)]" + + return self.domain(env[self.model_name]) if callable(self.domain) else self.domain + + +if "multi_pms_properties" in config.get("server_wide_modules"): + _logger = logging.getLogger(__name__) + _logger.info("monkey patching fields._Relational") + + fields._Relational.check_pms_properties = False + fields._Relational._description_domain = _description_domain diff --git a/multi_pms_properties/__manifest__.py b/multi_pms_properties/__manifest__.py new file mode 100644 index 0000000000..4c5f81107b --- /dev/null +++ b/multi_pms_properties/__manifest__.py @@ -0,0 +1,17 @@ +# © 2013 Therp BV +# © 2014 ACSONE SA/NV +# Copyright 2018 Quartile Limited +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + "name": "multi_pms_properties", + "summary": "Multi Properties Manager", + "version": "14.0.1.0.0", + "website": "https://github.com/OCA/pms", + "author": "Commit [Sun], Odoo Community Association (OCA)", + "license": "AGPL-3", + "category": "Pms", + "depends": ["base"], + "auto_install": False, + "installable": True, +} diff --git a/multi_pms_properties/i18n/multi_pms_properties.pot b/multi_pms_properties/i18n/multi_pms_properties.pot new file mode 100644 index 0000000000..a11d509b21 --- /dev/null +++ b/multi_pms_properties/i18n/multi_pms_properties.pot @@ -0,0 +1,49 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * multi_pms_properties +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: multi_pms_properties +#: code:addons/multi_pms_properties/models.py:0 +#, python-format +msgid "" +"- %(record)r belongs to properties %(pms_properties)r and\n" +" %(field)r (%(fname)s: %(values)s) belongs to another properties." +msgstr "" + +#. module: multi_pms_properties +#: code:addons/multi_pms_properties/models.py:0 +#, python-format +msgid "" +"- Record is properties %(pms_properties)r and %(field)r\n" +" (%(fname)s: %(values)s) belongs to another properties." +msgstr "" + +#. module: multi_pms_properties +#: model:ir.model,name:multi_pms_properties.model_base +msgid "Base" +msgstr "" + +#. module: multi_pms_properties +#: code:addons/multi_pms_properties/models.py:0 +#, python-format +msgid "Incompatible properties on records:" +msgstr "" + +#. module: multi_pms_properties +#: code:addons/multi_pms_properties/models.py:0 +#, python-format +msgid "" +"You cannot establish a company other than the one with the established " +"properties" +msgstr "" diff --git a/multi_pms_properties/models.py b/multi_pms_properties/models.py new file mode 100644 index 0000000000..25a475e43d --- /dev/null +++ b/multi_pms_properties/models.py @@ -0,0 +1,192 @@ +# Copyright 2021 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, api, models +from odoo.exceptions import UserError + + +class BaseModel(models.AbstractModel): + _inherit = "base" + _check_pms_properties_auto = False + """On write and create, call ``_check_pms_properties_auto`` to ensure properties + consistency on the relational fields having ``check_pms_properties=True`` + as attribute. + """ + + def _valid_field_parameter(self, field, name): + """Make new field attribute valid for Odoo.""" + return name == "check_pms_properties" or super()._valid_field_parameter( + field, name + ) + + @api.model_create_multi + def create(self, vals_list): + records = super(BaseModel, self).create(vals_list) + if self._check_pms_properties_auto: + records._check_pms_properties() + return records + + def write(self, vals): + res = super(BaseModel, self).write(vals) + check_pms_properties = False + for fname in vals: + field = self._fields.get(fname) + if ( + fname == "pms_property_id" + or fname == "pms_property_ids" + or fname == "company_id" + or (field.relational and getattr(field, "check_pms_properties", False)) + ): + check_pms_properties = True + if res and check_pms_properties and self._check_pms_properties_auto: + self._check_pms_properties() + return res + + def _check_pms_properties(self, fnames=None): + """Check the properties of the values of the given field names. + + :param list fnames: names of relational fields to check + :raises UserError: if the `pms_properties` of the value of any field is not + in `[False, self.pms_property_id]` (or `self` if + :class:`~odoo.addons.base.models.pms_property`). + + For :class:`~odoo.addons.base.models.res_users` relational fields, + verifies record company is in `company_ids` fields. + + User with main pms property A, having access to pms property A and B, could be + assigned or linked to records in property B. + """ + if fnames is None: + fnames = self._fields + + regular_fields = self._get_regular_fields(fnames) + + if not regular_fields: + return + + inconsistencies = self._check_inconsistencies(regular_fields) + + if inconsistencies: + lines = [_("Incompatible properties on records:")] + property_msg = _( + """- Record is properties %(pms_properties)r and %(field)r + (%(fname)s: %(values)s) belongs to another properties.""" + ) + record_msg = _( + """- %(record)r belongs to properties %(pms_properties)r and + %(field)r (%(fname)s: %(values)s) belongs to another properties.""" + ) + for record, name, corecords in inconsistencies[:5]: + if record._name == "pms.property": + msg, pms_properties = property_msg, record + else: + msg, pms_properties = ( + record_msg, + record.pms_property_id.name + if "pms_property_id" in record + else ", ".join(repr(p.name) for p in record.pms_property_ids), + ) + field = self.env["ir.model.fields"]._get(self._name, name) + lines.append( + msg + % { + "record": record.display_name, + "pms_properties": pms_properties, + "field": field.field_description, + "fname": field.name, + "values": ", ".join( + repr(rec.display_name) for rec in corecords + ), + } + ) + raise UserError("\n".join(lines)) + + def _get_regular_fields(self, fnames): + regular_fields = [] + for name in fnames: + field = self._fields[name] + if ( + field.relational + and getattr(field, "check_pms_properties", False) + and ( + "pms_property_id" in self.env[field.comodel_name] + or "pms_property_ids" in self.env[field.comodel_name] + ) + ): + regular_fields.append(name) + return regular_fields + + def _check_inconsistencies(self, regular_fields): + inconsistencies = [] + for record in self: + pms_properties = False + if record._name == "pms.property": + pms_properties = record + if "pms_property_id" in record: + pms_properties = record.pms_property_id + if "pms_property_ids" in record: + pms_properties = record.pms_property_ids + # Check the property & company consistence + if "company_id" in self._fields: + if record.company_id and pms_properties: + property_companies = pms_properties.mapped("company_id.id") + if ( + len(property_companies) > 1 + or record.company_id.id != property_companies[0] + ): + raise UserError( + _( + "You cannot establish a company other than " + "the one with the established properties" + ) + ) + # Check verifies that all + # records linked via relation fields are compatible + # with the properties of the origin document, + for name in regular_fields: + field = self._fields[name] + co_pms_properties = False + + corecord = record.sudo()[name] + # TODO:res.users management properties + if "pms_property_id" in corecord: + co_pms_properties = corecord.pms_property_id + if "pms_property_ids" in corecord: + co_pms_properties = corecord.pms_property_ids + if ( + # There is an inconsistency if: + # + # - Record has properties and corecord too and + # there's no match between them: + # X Pms_room_class with Property1 cannot contain + # Pms_room with property2 X + # + # - Record has a relation one2many with corecord and + # corecord properties aren't included in record properties + # or what is the same, subtraction between corecord properties + # and record properties must be False: + # X Pricelist with Prop1 and Prop2 cannot contain + # Pricelist_item with Prop1 and Prop3 X + # X Pricelist with Prop1 and Prop2 cannot contain + # Pricelist_item with Prop1, Prop2 and Prop3 X + # -In case that record has a relation many2one + # with corecord the condition is the same as avobe + ( + pms_properties + and co_pms_properties + and (not pms_properties & co_pms_properties) + ) + or ( + corecord + and field.type == "one2many" + and pms_properties + and (co_pms_properties - pms_properties) + ) + or ( + field.type == "many2one" + and co_pms_properties + and ((pms_properties - co_pms_properties) or not pms_properties) + ) + ): + inconsistencies.append((record, name, corecord)) + return inconsistencies diff --git a/multi_pms_properties/readme/CONTRIBUTORS.rst b/multi_pms_properties/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..eb93871f1e --- /dev/null +++ b/multi_pms_properties/readme/CONTRIBUTORS.rst @@ -0,0 +1,5 @@ +* `Commit [Sun] `: + + * Dario Lodeiros + * Eric Antones + * Sara Lago diff --git a/multi_pms_properties/readme/DESCRIPTION.rst b/multi_pms_properties/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..d0d40fd9d0 --- /dev/null +++ b/multi_pms_properties/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +Technical addon to support multiproperty in property management system (PMS). diff --git a/multi_pms_properties/readme/INSTALL.rst b/multi_pms_properties/readme/INSTALL.rst new file mode 100644 index 0000000000..b6b2f665f2 --- /dev/null +++ b/multi_pms_properties/readme/INSTALL.rst @@ -0,0 +1,7 @@ +To install this module, you only need to add it to your addons, and load it as +a server-wide module. + +This can be done with the ``server_wide_modules`` parameter in ``/etc/odoo.conf`` +or with the ``--load`` command-line parameter + +``server_wide_modules = "multi_pms_properties"`` diff --git a/multi_pms_properties/readme/USAGE.rst b/multi_pms_properties/readme/USAGE.rst new file mode 100644 index 0000000000..daa058b5f3 --- /dev/null +++ b/multi_pms_properties/readme/USAGE.rst @@ -0,0 +1,5 @@ +* Use the standard multicompany guidelines applied to pms.property: + + ``_check_pms_properties_auto like model attribute to autocheck on create/write`` + ``check_pms_properties like field attribute to check relational record properties consistence`` + ``This module not implement propety dependent fields`` diff --git a/multi_pms_properties/static/description/icon.png b/multi_pms_properties/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/multi_pms_properties/static/description/icon.png differ diff --git a/multi_pms_properties/static/description/index.html b/multi_pms_properties/static/description/index.html new file mode 100644 index 0000000000..0027c50c98 --- /dev/null +++ b/multi_pms_properties/static/description/index.html @@ -0,0 +1,446 @@ + + + + + + +multi_pms_properties + + + +
+

multi_pms_properties

+ + +

Beta License: AGPL-3 OCA/pms Translate me on Weblate Try me on Runboat

+

Technical addon to support multiproperty in property management system (PMS).

+

Table of contents

+ +
+

Installation

+

To install this module, you only need to add it to your addons, and load it as +a server-wide module.

+

This can be done with the server_wide_modules parameter in /etc/odoo.conf +or with the --load command-line parameter

+

server_wide_modules = "multi_pms_properties"

+
+
+

Usage

+
    +
  • Use the standard multicompany guidelines applied to pms.property:

    +

    _check_pms_properties_auto like model attribute to autocheck  on create/write +check_pms_properties like field attribute to check relational record properties consistence +This module not implement propety dependent fields

    +
  • +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Commit [Sun]
  • +
+
+
+

Contributors

+
    +
  • Commit [Sun] <https://www.commitsun.com>:
      +
    • Dario Lodeiros
    • +
    • Eric Antones
    • +
    • Sara Lago
    • +
    +
  • +
+
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/pms project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/multi_pms_properties/tests/__init__.py b/multi_pms_properties/tests/__init__.py new file mode 100644 index 0000000000..c80a139bc6 --- /dev/null +++ b/multi_pms_properties/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2021 Eric Antones +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from . import common +from . import test_multi_pms_properties diff --git a/multi_pms_properties/tests/common.py b/multi_pms_properties/tests/common.py new file mode 100644 index 0000000000..d4e9e21838 --- /dev/null +++ b/multi_pms_properties/tests/common.py @@ -0,0 +1,20 @@ +# Copyright 2021 Eric Antones +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + + +def setup_test_model(env, model_clses): + for model_cls in model_clses: + model_cls._build_model(env.registry, env.cr) + + env.registry.setup_models(env.cr) + env.registry.init_models( + env.cr, + [model_cls._name for model_cls in model_clses], + dict(env.context, update_custom_fields=True), + ) + + +def teardown_test_model(env, model_clses): + for model_cls in model_clses: + del env.registry.models[model_cls._name] + env.registry.setup_models(env.cr) diff --git a/multi_pms_properties/tests/multi_pms_properties_tester.py b/multi_pms_properties/tests/multi_pms_properties_tester.py new file mode 100644 index 0000000000..eabf61aa1c --- /dev/null +++ b/multi_pms_properties/tests/multi_pms_properties_tester.py @@ -0,0 +1,17 @@ +# Copyright 2021 Eric Antones +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ParentTester(models.Model): + _name = "pms.parent.tester" + + name = fields.Char(required=True) + + +class ChildTester(models.Model): + _name = "pms.child.tester" + + name = fields.Char(required=True) + parent_id = fields.Many2one("pms.parent.tester", check_pms_properties=True) diff --git a/multi_pms_properties/tests/test_multi_pms_properties.py b/multi_pms_properties/tests/test_multi_pms_properties.py new file mode 100644 index 0000000000..10f81e0226 --- /dev/null +++ b/multi_pms_properties/tests/test_multi_pms_properties.py @@ -0,0 +1,41 @@ +# Copyright 2021 Eric Antones +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo.tests import common + +from .common import setup_test_model # , teardown_test_model +from .multi_pms_properties_tester import ChildTester, ParentTester + +_logger = logging.getLogger(__name__) + + +@common.tagged("-at_install", "post_install") +class TestMultiPMSProperties(common.SavepointCase): + @classmethod + def setUpClass(cls): + super(TestMultiPMSProperties, cls).setUpClass() + model_classes = [ParentTester, ChildTester] + setup_test_model(cls.env, model_classes) + for mdl_cls in model_classes: + tester_model = cls.env["ir.model"].search([("model", "=", mdl_cls._name)]) + # Access record + cls.env["ir.model.access"].create( + { + "name": "access.%s" % mdl_cls._name, + "model_id": tester_model.id, + "perm_read": 1, + "perm_write": 1, + "perm_create": 1, + "perm_unlink": 1, + } + ) + + # @classmethod + # def tearDownClass(cls): + # teardown_test_model(cls.env, [ParentTester]) + # super(TestMultiPMSProperties, cls).tearDownClass() + + # def test_exist_attribute(self): + # parent = self.env["pms.parent.tester"].create({"name": "parent test"}) diff --git a/pms/README.rst b/pms/README.rst new file mode 100644 index 0000000000..c0ef973cb8 --- /dev/null +++ b/pms/README.rst @@ -0,0 +1,108 @@ +================================ +PMS (Property Management System) +================================ + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:4f0393d5fcb9c4849b44b6dc0a699e103b2bacebc46eec3b9c8cccbc3030a628 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fpms-lightgray.png?logo=github + :target: https://github.com/OCA/pms/tree/16.0/pms + :alt: OCA/pms +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/pms-16-0/pms-16-0-pms + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/pms&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module is an all-in-one property management system (PMS) focused on medium-sized properties +for managing every aspect of your property's daily operations. + +You can manage properties with multi-property and multi-company support, including your rooms inventory, +reservations, check-in, daily reports, board services, rate and availability plans among other property functionalities. + +**Table of contents** + +.. contents:: + :local: + +Installation +============ + +This module depends on modules ``base``, ``mail``, ``sale`` and ``multi_pms_properties``. +Ensure yourself to have all them in your addons list. + +Configuration +============= + +You will find the hotel settings in PMS Management > Configuration > Properties > Your Property. + +This module required additional configuration for company, accounting, invoicing and user privileges. + +Usage +===== + +To use this module, please, read the complete user guide at ``_. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Commit [Sun] + +Contributors +~~~~~~~~~~~~ + +* Alexandre Díaz +* Pablo Quesada +* Jose Luis Algara +* `Commit [Sun] `: + + * Dario Lodeiros + * Eric Antones + * Sara Lago + * Brais Abeijon + * Miguel Padin +* Omar Castiñeira + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/pms `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/pms/__init__.py b/pms/__init__.py new file mode 100644 index 0000000000..a4616f410f --- /dev/null +++ b/pms/__init__.py @@ -0,0 +1,6 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models +from . import wizards +from . import controllers +from .init_hook import pre_init_hook diff --git a/pms/__manifest__.py b/pms/__manifest__.py new file mode 100644 index 0000000000..84048192da --- /dev/null +++ b/pms/__manifest__.py @@ -0,0 +1,122 @@ +# Copyright 2019 Darío Lodeiros, Alexandre Díaz, Jose Luis Algara, Pablo Quesada +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "PMS (Property Management System)", + "summary": "A property management system", + "version": "16.0.0.1.0", + "development_status": "Beta", + "category": "Generic Modules/Property Management System", + "website": "https://github.com/OCA/pms", + "author": "Commit [Sun], Odoo Community Association (OCA)", + "license": "AGPL-3", + "application": True, + "installable": True, + "depends": [ + "base", + "base_automation", + "mail", + # "account_payment_return", + # "email_template_qweb", + "sale", + "multi_pms_properties", + "partner_firstname", + "partner_second_lastname", + "partner_contact_gender", + "partner_contact_birthdate", + "partner_contact_nationality", + "account_reconcile_oca", + # "partner_identification_unique_by_category", + "queue_job", + "web_timeline", + "partner_identification", + ], + "data": [ + "security/pms_security.xml", + "security/ir.model.access.csv", + "data/cron_jobs.xml", + "data/pms_sequence.xml", + "data/pms_confirmed_reservation_email_template.xml", + "data/pms_modified_reservation_email_template.xml", + "data/pms_cancelled_reservation_email_template.xml", + "data/pms_precheckin_invitation_email_template.xml", + "data/pms_data.xml", + "data/traveller_report_paperformat.xml", + "report/pms_folio.xml", + "report/pms_folio_templates.xml", + "report/traveller_report_action.xml", + "report/invoice.xml", + # "templates/pms_email_template.xml", + "data/menus.xml", + "data/queue_data.xml", + "data/queue_job_function_data.xml", + "wizards/wizard_payment_folio.xml", + "wizards/folio_make_invoice_advance_views.xml", + "wizards/pms_booking_engine_views.xml", + "wizards/wizard_folio_changes.xml", + "wizards/wizard_several_partners.xml", + "wizards/pms_booking_duplicate_views.xml", + "views/pms_amenity_views.xml", + "views/pms_amenity_type_views.xml", + "views/pms_board_service_views.xml", + "views/pms_board_service_room_type_views.xml", + "views/pms_cancelation_rule_views.xml", + "views/pms_checkin_partner_views.xml", + "views/pms_ubication_views.xml", + "views/pms_property_views.xml", + "views/pms_reservation_views.xml", + "views/pms_service_views.xml", + "views/pms_service_line_views.xml", + "views/pms_folio_views.xml", + "views/pms_room_type_views.xml", + "views/pms_room_views.xml", + "views/pms_room_closure_reason_views.xml", + "views/account_payment_views.xml", + "views/account_move_views.xml", + # "views/account_bank_statement_views.xml", + "views/res_users_views.xml", + "views/pms_room_type_class_views.xml", + "views/pms_availability_plan_views.xml", + "views/pms_availability_plan_rule_views.xml", + "views/res_partner_views.xml", + "views/product_pricelist_views.xml", + "views/product_pricelist_item_views.xml", + "views/pms_sale_channel.xml", + "views/product_template_views.xml", + # "views/webclient_templates.xml", + "views/account_journal_views.xml", + "views/folio_portal_templates.xml", + "views/reservation_portal_templates.xml", + "views/res_company_views.xml", + "views/traveller_report_template.xml", + # "views/assets.xml", + "wizards/wizard_split_join_swap_reservation.xml", + "views/precheckin_portal_templates.xml", + "wizards/wizard_massive_changes.xml", + "wizards/wizard_advanced_filters.xml", + "views/payment_transaction_views.xml", + "views/account_move_line_views.xml", + "report/proforma_report_templates.xml", + "report/proforma_report.xml", + "views/account_portal_templates.xml", + "views/payment_acquirer_views.xml", + # "views/account_analytic_distribution_views.xml", + # "views/account_analytic_line_views.xml", + "views/res_partner_category.xml", + "views/res_partner_id_category_views.xml", + "views/res_partner_id_number_views.xml", + "views/res_country_views.xml", + ], + "demo": [ + "demo/pms_master_data_no_update.xml", + "demo/pms_master_data.xml", + "demo/pms_folio.xml", + "demo/pms_reservation.xml", + ], + "qweb": [ + "static/src/xml/pms_base_templates.xml", + "static/src/xml/reservation_group_button_views.xml", + "static/src/xml/account_reconciliation.xml", + ], + "pre_init_hook": "pre_init_hook", +} diff --git a/pms/controllers/__init__.py b/pms/controllers/__init__.py new file mode 100644 index 0000000000..52d463f19b --- /dev/null +++ b/pms/controllers/__init__.py @@ -0,0 +1 @@ +from . import pms_portal diff --git a/pms/controllers/pms_portal.py b/pms/controllers/pms_portal.py new file mode 100644 index 0000000000..f7ba824be5 --- /dev/null +++ b/pms/controllers/pms_portal.py @@ -0,0 +1,619 @@ +from odoo import _, http +from odoo.exceptions import AccessError, MissingError +from odoo.http import request + +from odoo.addons.account.controllers.portal import PortalAccount + +# from odoo.addons.payment.controllers.portal import PaymentProcessing +from odoo.addons.portal.controllers.portal import CustomerPortal, pager as portal_pager +from odoo.addons.portal.models.portal_mixin import PortalMixin + + +class PortalFolio(CustomerPortal): + def _prepare_home_portal_values(self, counters): + partner = request.env.user.partner_id + values = super()._prepare_home_portal_values(counters) + Folio = request.env["pms.folio"] + if "folio_count" in counters: + values["folio_count"] = ( + Folio.search_count( + [ + ("partner_id", "=", partner.id), + ] + ) + if Folio.check_access_rights("read", raise_exception=False) + else 0 + ) + return values + + def _folio_get_page_view_values(self, folio, access_token, **kwargs): + values = {"folio": folio, "token": access_token} + payment_inputs = ( + request.env["payment.provider"] + .sudo() + ._get_available_payment_input( + partner=folio.partner_id, company=folio.company_id + ) + ) + acquirers = payment_inputs.get("acquirers") + for acquirer in acquirers: + if ( + acquirer.pms_property_ids + and folio.pms_property_id.id not in acquirer.pms_property_ids.ids + ): + payment_inputs["acquirers"] -= acquirer + values.update(payment_inputs) + is_public_user = request.env.user._is_public() + if is_public_user: + payment_inputs.pop("pms", None) + token_count = ( + request.env["payment.token"] + .sudo() + .search_count( + [ + ("acquirer_id.company_id", "=", folio.company_id.id), + ("partner_id", "=", folio.partner_id.id), + "|", + ( + "acquirer_id.pms_property_ids", + "in", + folio.pms_property_id.id, + ), + ("acquirer_id.pms_property_ids", "=", False), + ] + ) + ) + values["existing_token"] = token_count > 0 + values.update(payment_inputs) + values["partner_id"] = ( + folio.partner_id if is_public_user else request.env.user.partner_id, + ) + return self._get_page_view_values( + folio, access_token, values, "my_folios_history", False, **kwargs + ) + + @http.route( + "/folio/pay//form_tx", type="json", auth="public", website=True + ) + def folio_pay_form( + self, acquirer_id, folio_id, save_token=False, access_token=None, **kwargs + ): + folio_sudo = request.env["pms.folio"].sudo().browse(folio_id) + if not folio_sudo: + return False + + try: + acquirer_id = int(acquirer_id) + except Exception: + return False + + if request.env.user._is_public(): + save_token = False # we avoid to create a token for the public user + + success_url = kwargs.get( + "success_url", + "%s?%s" % (folio_sudo.access_url, access_token if access_token else ""), + ) + custom_amount = False + if "custom_amount" in kwargs: + custom_amount = float(kwargs["custom_amount"]) + + vals = { + "acquirer_id": acquirer_id, + "return_url": success_url, + } + + if save_token: + vals["type"] = "form_save" + transaction = folio_sudo._create_payment_transaction(vals) + # PaymentProcessing.add_payment_transaction(transaction) + if not transaction: + return False + tx_ids_list = set(request.session.get("__payment_tx_ids__", [])) | set( + transaction.ids + ) + request.session["__payment_tx_ids__"] = list(tx_ids_list) + return transaction.render_folio_button( + folio_sudo, + submit_txt=_("Pay & Confirm"), + render_values={ + "type": "form_save" if save_token else "form", + "alias_usage": _( + "If we store your payment information on our server, " + "subscription payments will be made automatically." + ), + }, + custom_amount=custom_amount, + ) + + @http.route( + ["/my/folios", "/my/folios/page/"], + type="http", + auth="public", + website=True, + ) + def portal_my_folios( + self, page=1, date_begin=None, date_end=None, sortby=None, filterby=None, **kw + ): + partner = request.env.user.partner_id + values = self._prepare_portal_layout_values() + PmsFolio = request.env["pms.folio"] + values["folios"] = PmsFolio.search( + [ + ("partner_id", "child_of", partner.id), + ] + ) + domain = [ + ("partner_id", "child_of", partner.id), + ] + searchbar_sortings = { + "date": {"label": _("Order Date"), "folio": "date_order desc"}, + "name": {"label": _("Reference"), "folio": "name"}, + "stage": {"label": _("Stage"), "folio": "state"}, + } + if not sortby: + sortby = "date" + sort_order = searchbar_sortings[sortby]["folio"] + + if date_begin and date_end: + domain += [ + ("create_date", ">", date_begin), + ("create_date", "<=", date_end), + ] + folio_count = PmsFolio.search_count(domain) + pager = portal_pager( + url="/my/folios", + url_args={"date_begin": date_begin, "date_end": date_end, "sortby": sortby}, + total=folio_count, + page=page, + step=self._items_per_page, + ) + folios = PmsFolio.search( + domain, order=sort_order, limit=self._items_per_page, offset=pager["offset"] + ) + request.session["my_folios_history"] = folios.ids[:100] + values.update( + { + "date": date_begin, + "folios": folios.sudo(), + "page_name": "folios", + "pager": pager, + "default_url": "/my/folios", + "searchbar_sortings": searchbar_sortings, + "sortby": sortby, + } + ) + return request.render("pms.portal_my_folio", values) + + @http.route(["/my/folios/"], type="http", auth="public", website=True) + def portal_my_folio_detail( + self, folio_id, access_token=None, report_type=None, download=False, **kw + ): + try: + folio_sudo = self._document_check_access( + "pms.folio", + folio_id, + access_token=access_token, + ) + except (AccessError, MissingError): + return request.redirect("/my") + if report_type in ("html", "pdf", "text"): + return self._show_report( + model=folio_sudo, + report_type=report_type, + report_ref="pms.action_report_folio", + download=download, + ) + values = self._folio_get_page_view_values(folio_sudo, access_token, **kw) + if "custom_amount" in kw: + values["custom_amount"] = float(kw["custom_amount"]) + return request.render("pms.folio_portal_template", values) + + +class PortalReservation(CustomerPortal): + def _prepare_home_portal_values(self, counters): + partner = request.env.user.partner_id + values = super()._prepare_home_portal_values(counters) + Reservation = request.env["pms.reservation"] + if "reservation_count" in counters: + values["reservation_count"] = ( + Reservation.search_count( + [ + ("partner_id", "=", partner.id), + ] + ) + if Reservation.check_access_rights("read", raise_exception=False) + else 0 + ) + return values + + def _reservation_get_page_view_values(self, reservation, access_token, **kwargs): + values = {"reservation": reservation, "token": access_token} + return self._get_page_view_values( + reservation, + access_token, + values, + "my_reservations_history", + False, + **kwargs + ) + + @http.route( + ["/my/reservations", "/my/reservations/page/"], + type="http", + auth="public", + website=True, + ) + def portal_my_reservations(self, page=1, date_begin=None, date_end=None): + partner = request.env.user.partner_id + values = self._prepare_portal_layout_values() + Reservation = request.env["pms.reservation"] + values["reservations"] = Reservation.search( + [ + ("partner_id", "child_of", partner.id), + ] + ) + domain = [ + ("partner_id", "child_of", partner.id), + ] + if date_begin and date_end: + domain += [ + ("create_date", ">", date_begin), + ("create_date", "<=", date_end), + ] + reservation_count = Reservation.search_count(domain) + pager = portal_pager( + url="/my/reservations", + url_args={"date_begin": date_begin, "date_end": date_end}, + total=reservation_count, + page=page, + step=self._items_per_page, + ) + reservations = Reservation.search( + domain, limit=self._items_per_page, offset=pager["offset"] + ) + folios_dict = {} + for reservation in reservations: + folio = reservation.folio_id + folios_dict[folio] = "" + + request.session["my_reservations_history"] = reservations.ids[:100] + values.update( + { + "date": date_begin, + "reservations": reservations.sudo(), + "page_name": "reservations", + "pager": pager, + "default_url": "/my/reservations", + "folios_dict": folios_dict, + "partner": partner, + } + ) + return request.render("pms.portal_my_reservation", values) + + @http.route( + ["/my/reservations/"], + type="http", + auth="public", + website=True, + ) + def portal_my_reservation_detail(self, reservation_id, access_token=None, **kw): + try: + reservation_sudo = self._document_check_access( + "pms.reservation", + reservation_id, + access_token=access_token, + ) + except (AccessError, MissingError): + return request.redirect("/my") + values = self._reservation_get_page_view_values( + reservation_sudo, access_token, **kw + ) + return request.render("pms.portal_my_reservation_detail", values) + + +class PortalPrecheckin(CustomerPortal): + def _precheckin_get_page_view_values( + self, checkin_partner_id, access_token, **kwargs + ): + checkin_partner = request.env["pms.checkin.partner"].browse(checkin_partner_id) + values = {"checkin_partner_id": checkin_partner, "token": access_token} + return self._get_page_view_values( + checkin_partner, + access_token, + values, + "my_precheckins_history", + False, + **kwargs + ) + + @http.route( + ["/my/folios//precheckin"], + type="http", + auth="public", + website=True, + ) + def portal_my_precheckin( + self, + folio_id, + access_token=None, + ): + country_ids = request.env["res.country"].search([]) + state_ids = request.env["res.country.state"].search([]) + doc_type_ids = request.env["res.partner.id_category"].sudo().search([]) + values = self._prepare_portal_layout_values() + try: + folio_sudo = self._document_check_access( + "pms.folio", + folio_id, + access_token=access_token, + ) + except (AccessError, MissingError): + return request.render("pms.portal_not_checkin", values) + available_checkins = folio_sudo.checkin_partner_ids.filtered( + lambda c: c.state in ["dummy", "draft"] + ) + checkin_partner = ( + available_checkins[0] + if available_checkins + else folio_sudo.checkin_partner_ids[0] + ) + values.update( + { + "error": {}, + "country_ids": country_ids, + "state_ids": state_ids, + "doc_type_ids": doc_type_ids, + "folio": folio_sudo, + "checkin_partner_id": checkin_partner, + } + ) + if checkin_partner.state not in ["dummy", "draft"]: + return request.render("pms.portal_not_checkin", values) + return request.render("pms.portal_my_reservation_precheckin", values) + + @http.route( + ["/my/folios//reservations"], + type="http", + auth="public", + website=True, + csrf=False, + ) + def portal_precheckin_folio(self, folio_id, access_token=None, **kw): + values = self._prepare_portal_layout_values() + try: + folio_sudo = self._document_check_access( + "pms.folio", + folio_id, + access_token=access_token, + ) + except (AccessError, MissingError): + return request.redirect("/my") + values.update({"no_breadcrumbs": True, "folio": folio_sudo}) + return request.render("pms.portal_my_prechekin_folio", values) + + @http.route( + ["/my/folios//reservations//checkins"], + type="http", + auth="public", + website=True, + csrf=False, + ) + def portal_precheckin_reservation( + self, folio_id, reservation_id, access_token=None, **kw + ): + folio = request.env["pms.folio"].sudo().browse(folio_id) + reservation = request.env["pms.reservation"].sudo().browse(reservation_id) + values = {} + values.update({"folio": folio}) + values.update( + { + "no_breadcrumbs": True, + "folio_access_token": access_token, + "reservation": reservation, + } + ) + return request.render("pms.portal_my_prechekin_reservation", values) + + @http.route( + [ + "/my/folios/" + "/reservations/" + "/checkins/" + ], + type="http", + auth="public", + website=True, + csrf=False, + ) + def portal_precheckin( + self, folio_id, reservation_id, checkin_partner_id, access_token=None, **kw + ): + folio = request.env["pms.folio"].sudo().browse(folio_id) + reservation = request.env["pms.reservation"].sudo().browse(reservation_id) + try: + checkin_sudo = self._document_check_access( + "pms.checkin.partner", + checkin_partner_id, + access_token=access_token, + ) + except (AccessError, MissingError): + return request.render("pms.portal_not_checkin", kw) + values = {} + zip_ids = request.env["res.city.zip"].search([]) + country_ids = request.env["res.country"].search([]) + state_ids = request.env["res.country.state"].search([]) + city_ids = request.env["res.city"].search([]) + doc_type_ids = request.env["res.partner.id_category"].sudo().search([]) + access_token = checkin_sudo.access_token + if not checkin_sudo.access_token: + access_token = PortalMixin._portal_ensure_token(checkin_sudo) + values.update( + self._precheckin_get_page_view_values(checkin_sudo.id, access_token) + ) + values.update( + { + "folio_access_token": kw.get("folio_access_token"), + "no_breadcrumbs": True, + "folio": folio, + "reservation": reservation, + "checkin_partner": checkin_sudo, + "zip_ids": zip_ids, + "country_ids": country_ids, + "state_ids": state_ids, + "city_ids": city_ids, + "doc_type_ids": doc_type_ids, + } + ) + if checkin_sudo.state not in ["dummy", "draft"]: + return request.render("pms.portal_not_checkin", values) + + return request.render("pms.portal_my_precheckin_detail", values) + + @http.route( + [ + "/my/folios/" + "/reservations/" + "/checkins//submit" + ], + type="http", + auth="public", + website=True, + csrf=False, + ) + def portal_precheckin_submit( + self, folio_id, reservation_id, checkin_partner_id, **kw + ): + checkin_partner = ( + request.env["pms.checkin.partner"].sudo().browse(checkin_partner_id) + ) + + values = kw + values.update( + { + "checkin_partner": checkin_partner, + } + ) + folio_access_token = values.get("folio_access_token") + request.env["pms.checkin.partner"]._save_data_from_portal(kw) + folio = request.env["pms.folio"].sudo().browse(folio_id) + reservation = request.env["pms.reservation"].sudo().browse(reservation_id) + values.update( + { + "no_breadcrumbs": True, + "folio": folio, + "reservation": reservation, + } + ) + + if folio_access_token: + return request.render("pms.portal_my_prechekin_reservation", values) + else: + return request.render("pms.portal_my_precheckin_end", values) + + @http.route( + ["/my/folios//invitations"], + type="http", + auth="public", + website=True, + csrf=False, + ) + def portal_precheckin_invitation(self, folio_id, access_token=None, **kw): + try: + folio_sudo = self._document_check_access( + "pms.folio", + folio_id, + access_token=access_token, + ) + except (AccessError, MissingError): + return request.redirect("/my") + web_url = ( + request.env["ir.config_parameter"] + .sudo() + .search([("key", "=", "web.base.url")]) + ) + values = self._folio_get_page_view_values(folio_sudo, access_token, **kw) + values.update({"no_breadcrumbs": True, "error": {}, "web_url": web_url.value}) + return request.render("pms.portal_my_folio_invitations", values) + + @http.route( + ["/my/precheckin/send_invitation"], + auth="public", + type="json", + website=True, + csrf=False, + ) + def portal_precheckin_folio_send_invitation(self, **kw): + if kw.get("folio_id"): + folio = request.env["pms.folio"].browse(int(kw.get("folio_id"))) + kw.update({"folio": folio}) + checkin_partner = ( + request.env["pms.checkin.partner"] + .sudo() + .browse(int(kw["checkin_partner_id"])) + ) + firstname = kw["firstname"] + email = kw["email"] + if firstname and email: + checkin_partner.write({"firstname": firstname, "email": email}) + checkin_partner.send_portal_invitation_email(firstname, email) + + +class PortalAccount(PortalAccount): + @http.route( + ["/my/invoices/proforma/"], + type="http", + auth="public", + website=True, + ) + def portal_proforma_my_invoice_detail( + self, invoice_id, access_token=None, report_type=None, download=False, **kw + ): + try: + invoice_sudo = self._document_check_access( + "account.move", invoice_id, access_token + ) + except (AccessError, MissingError): + return request.redirect("/my") + + if report_type in ("html", "pdf", "text"): + return self._show_report( + model=invoice_sudo, + report_type=report_type, + report_ref="pms.action_report_pms_pro_forma_invoice", + download=download, + ) + + invoice_sudo = invoice_sudo.with_context(proforma=True) + values = self._invoice_get_page_view_values(invoice_sudo, access_token, **kw) + acquirers = values.get("acquirers") + if acquirers: + country_id = ( + values.get("partner_id") and values.get("partner_id")[0].country_id.id + ) + values["acq_extra_fees"] = acquirers.get_acquirer_extra_fees( + invoice_sudo.amount_residual, invoice_sudo.currency_id, country_id + ) + return request.render("pms.pms_proforma_invoice_template", values) + + def _invoice_get_page_view_values(self, invoice, access_token, **kwargs): + """ + Override to add the pms property filter + """ + values = super(PortalAccount, self)._invoice_get_page_view_values( + invoice, access_token, **kwargs + ) + acquirers = values.get("acquirers") + if acquirers: + for acquirer in acquirers: + if ( + acquirer.pms_property_ids + and invoice.pms_property_id.id not in acquirer.pms_property_ids.ids + ): + values["acquirers"] -= acquirer + payment_tokens = values.get("payment_tokens") + if payment_tokens: + for pms in payment_tokens: + if pms.acquirer_id not in values["acquirers"].ids: + values["pms"] -= pms + return values diff --git a/pms/data/cron_jobs.xml b/pms/data/cron_jobs.xml new file mode 100644 index 0000000000..6ee70df83a --- /dev/null +++ b/pms/data/cron_jobs.xml @@ -0,0 +1,120 @@ + + + + + Automatic No Show Reservation + 1 + + days + -1 + + code + + + model.auto_arrival_delayed() + + + + Automatic No Checkout Reservations + 5 + + minutes + -1 + + code + + + model.auto_departure_delayed() + + + + Recompute priority on reservations + 1 + + days + -1 + + code + + + model.update_daily_priority_reservation() + + + + + Auto Invoicing Folios + 1 + + + days + -1 + + code + + + model.autoinvoicing() + + + Auto Invoicing DownPayments + 1 + + + days + -1 + + code + + + model.auto_invoice_downpayments(offset=1) + + diff --git a/pms/data/icon-door.svg b/pms/data/icon-door.svg new file mode 100644 index 0000000000..05779c3138 --- /dev/null +++ b/pms/data/icon-door.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/pms/data/icon-mail.svg b/pms/data/icon-mail.svg new file mode 100644 index 0000000000..cee224e427 --- /dev/null +++ b/pms/data/icon-mail.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/pms/data/icon-phone.svg b/pms/data/icon-phone.svg new file mode 100644 index 0000000000..4bdec1694f --- /dev/null +++ b/pms/data/icon-phone.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/pms/data/menus.xml b/pms/data/menus.xml new file mode 100644 index 0000000000..46b5751a44 --- /dev/null +++ b/pms/data/menus.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + diff --git a/pms/data/pms_cancelled_reservation_email_template.xml b/pms/data/pms_cancelled_reservation_email_template.xml new file mode 100644 index 0000000000..4989e0f6d2 --- /dev/null +++ b/pms/data/pms_cancelled_reservation_email_template.xml @@ -0,0 +1,158 @@ + + + + Cancelled Reservation + + Your reservation in ${object.pms_property_id.name} has been cancelled + ${object.pms_property_id.partner_id.email | safe} + ${(object.email and '"%s" <%s>' % (object.name, object.email) or object.partner_id.email_formatted or '') | safe} + + + + + +
+ + + + + + + + + + + + +
+ + + +
+
+ % if object.pms_property_id.partner_id.street +

${object.pms_property_id.partner_id.street}

+ % endif + % if object.pms_property_id.partner_id.street2 +

${object.pms_property_id.partner_id.street2}

+ % endif +

${object.pms_property_id.partner_id.zip}

+

${object.pms_property_id.partner_id.city}

+

${object.pms_property_id.partner_id.country_id.name}

+
+
+
+
+ Hello ${object.partner_id.name or ''},
+ Your reservation at ${object.pms_property_id.name} has been cancelled. +
+
+
+ + +
+
+ +
+
If you have questions please contact with us:
+
    +
  • ${object.pms_property_id.name}
  • + % if object.pms_property_id.partner_id.email +
  • Mail: ${object.pms_property_id.partner_id.email}
  • + % endif + % if object.pms_property_id.partner_id.phone +
  • Phone: ${object.pms_property_id.partner_id.phone}
  • + % endif + % if object.pms_property_id.partner_id.mobile +
  • Mobile: ${object.pms_property_id.partner_id.mobile}
  • + % endif +
+
+
+
+
+ % if object.pms_property_id.privacy_policy + + +
+ ${object.pms_property_id.privacy_policy} +
+ % endif +
+ % if object.company_id + + +
+ Sent by ${object.company_id.name} +
+
+ % endif +
+
+ ${object.partner_id.lang} +
+
diff --git a/pms/data/pms_confirmed_reservation_email_template.xml b/pms/data/pms_confirmed_reservation_email_template.xml new file mode 100644 index 0000000000..185b79d18e --- /dev/null +++ b/pms/data/pms_confirmed_reservation_email_template.xml @@ -0,0 +1,308 @@ + + + + Confirmed Reservation + + ${object.company_id.name} has confirmed your reservation in ${object.pms_property_id.name} + ${object.pms_property_id.partner_id.email | safe} + ${(object.email and '"%s" <%s>' % (object.name, object.email) or object.partner_id.email_formatted or '') | safe} + + + + + +
+ + + + + + + + + + + + + + +
+ + + +
+
+ % if object.pms_property_id.partner_id.street +

${object.pms_property_id.partner_id.street}

+ % endif + % if object.pms_property_id.partner_id.street2 +

${object.pms_property_id.partner_id.street2}

+ % endif +

${object.pms_property_id.partner_id.zip}

+

${object.pms_property_id.partner_id.city}

+

${object.pms_property_id.partner_id.country_id.name}

+
+
+
+
+ Hello ${object.partner_id.name or ''},
+ We are happy to confirm your reservation in ${object.pms_property_id.name} +
+
+ See you soon,
+ +
+ ${object.company_id.name} +
+
+
+
+
+
Reservation Details
+ + + + + + + + +
+ + + + + + + + + + + + + +
+
+
+
From ${object.first_checkin}
+
To ${object.last_checkout}
+
TZ ${object.pms_property_id.tz}
+
+
+
+
+
+
Rooms:
+ % for reservation in object.reservation_ids: + ${reservation.room_type_id.name}
+ % endfor +
+
+
+
+
+
Price: ${object.amount_total} ${object.pms_property_id.country_id.currency_id.symbol}
+
+
+
+ % if object.pms_property_id.mail_information +
+
Additional Information
+ ${object.pms_property_id.mail_information|safe} + % endif +
+
+
+
+ Do your check-in now and save time. +
+ Access our quick registration system. In a few steps you will be able to register your data in an agile, simple and secure way, avoiding queues at reception. + If you register your data in our system, your passage through reception will be much faster, being able to enjoy the comfort of your room right away. + + + + +
+ +
Check-in +
+
Hacer check-in
+
+
+
+
+ +
+ Questions about the reservation? +
Please contact with us:
+
    +
  • ${object.pms_property_id.name}
  • + % if object.pms_property_id.partner_id.email +
  • Mail: ${object.pms_property_id.partner_id.email}
  • + % endif + % if object.pms_property_id.partner_id.phone +
  • Phone: ${object.pms_property_id.partner_id.phone}
  • + % endif + % if object.pms_property_id.partner_id.mobile +
  • Mobile: ${object.pms_property_id.partner_id.mobile}
  • + % endif +
+
+
+
+
+ % if object.pms_property_id.privacy_policy + + +
+ ${object.pms_property_id.privacy_policy|safe} +
+ % endif +
+ % if object.company_id + + +
+ Sent by ${object.company_id.name} +
+
+ % endif +
+
+ ${object.partner_id.lang} +
+
diff --git a/pms/data/pms_data.xml b/pms/data/pms_data.xml new file mode 100644 index 0000000000..b6718fe014 --- /dev/null +++ b/pms/data/pms_data.xml @@ -0,0 +1,141 @@ + + + + + Availability Plan + + + My Property + + + Rua Street Demo, s/n + Commitsun city + + 15703 + +34 123 456 879 + commitsun@hootel.com + https://www.commitsun.com + + + + + + + + + + Various Clients + Contact used for simplified invoices where no customer is available + + + + + + + + + + + + + + + + + + + Door + direct + + + + Phone + direct + + + + Mail + direct + + + + Agency + indirect + + + + Passport + P + 20 + + + + Driving License + C + 50 + +letters = { + 0: "T", + 1: "R", + 2: "W", + 3: "A", + 4: "G", + 5: "M", + 6: "Y", + 7: "F", + 8: "P", + 9: "D", + 10: "X", + 11: "B", + 12: "N", + 13: "J", + 14: "Z", + 15: "S", + 16: "Q", + 17: "V", + 18: "H", + 19: "L", + 20: "C", + 21: "K", + 22: "E", +} +dni_number = id_number.name[0:8] +dni_letter = id_number.name[ + len(id_number.name) - 1 : len(id_number.name) +] +if dni_number.isdigit() and not dni_letter.isdigit(): + if letters.get(int(dni_number) % 23) != dni_letter.upper(): + failed = True +else: + failed = True + + + + + Identification Document + I + 60 + + + + + European Residence permit + X + 40 + +permit_first_letter=id_number.name[0:1] +permit_last_letter = id_number.name[ + len(id_number.name) - 1 : len(id_number.name) +] +if (permit_first_letter.upper() in ['X','Y']) and id_number.name[1:8].isdigit() and not permit_last_letter.isdigit(): + failed = False +else: + failed = True + + + diff --git a/pms/data/pms_modified_reservation_email_template.xml b/pms/data/pms_modified_reservation_email_template.xml new file mode 100644 index 0000000000..e13e9d745f --- /dev/null +++ b/pms/data/pms_modified_reservation_email_template.xml @@ -0,0 +1,263 @@ + + + + Modified Reservation + + Your reservation in ${object.pms_property_id.name} has been modified + ${object.pms_property_id.partner_id.email | safe} + ${(object.email and '"%s" <%s>' % (object.name, object.email) or object.partner_id.email_formatted or '') | safe} + + + + + +
+ + + + + + + + + + + + + + + +
+ + + +
+
+ % if object.pms_property_id.partner_id.street +

${object.pms_property_id.partner_id.street}

+ % endif + % if object.pms_property_id.partner_id.street2 +

${object.pms_property_id.partner_id.street2}

+ % endif +

${object.pms_property_id.partner_id.zip}

+

${object.pms_property_id.partner_id.city}

+

${object.pms_property_id.partner_id.country_id.name}

+
+
+
+
+ Hello ${object.partner_id.name or ''},
+ Your reservation in ${object.pms_property_id.name} has been modified +
+
+ See you soon,
+ +
+ ${object.company_id.name} +
+
+
+
+
+
Reservation Details
+ + + + +
+ + + + + + + + + + + + + +
+
+
+
From ${object.first_checkin}
+
To ${object.last_checkout}
+
TZ ${object.pms_property_id.tz}
+
+
+
+
+
+
Rooms:
+ % for reservation in object.reservation_ids: + ${reservation.room_type_id.name}
+ % endfor +
+
+
+
+
+
Price: ${object.amount_total} ${object.pms_property_id.country_id.currency_id.symbol}
+
+
+
+ % if object.pms_property_id.mail_information +
+
Additional Information
+

${object.pms_property_id.mail_information|safe}

+ % endif +
+
+
+ +
+ Questions about the reservation? +
Please contact with us:
+
    +
  • ${object.pms_property_id.name}
  • + % if object.pms_property_id.partner_id.email +
  • Mail: ${object.pms_property_id.partner_id.email}
  • + % endif + % if object.pms_property_id.partner_id.phone +
  • Phone: ${object.pms_property_id.partner_id.phone}
  • + % endif + % if object.pms_property_id.partner_id.mobile +
  • Mobile: ${object.pms_property_id.partner_id.mobile}
  • + % endif +
+
+
+
+
+ % if object.pms_property_id.privacy_policy + + +
+ ${object.pms_property_id.privacy_policy|safe} +
+ % endif +
+ % if object.company_id + + +
+ Sent by ${object.company_id.name} +
+
+ % endif +
+
+ ${object.partner_id.lang} +
+
diff --git a/pms/data/pms_precheckin_invitation_email_template.xml b/pms/data/pms_precheckin_invitation_email_template.xml new file mode 100644 index 0000000000..cb6a5feda6 --- /dev/null +++ b/pms/data/pms_precheckin_invitation_email_template.xml @@ -0,0 +1,55 @@ + + + + Precheckin Invitation + + Hi ${object.firstname}, do your check-in now in ${object.pms_property_id.name} + ${object.pms_property_id.partner_id.email | safe} + ${(object.email and '"%s" <%s>' % (object.name, object.email) or object.partner_id.email_formatted or '') | safe} + +
+ Do your check-in now and save time. +
+ Access our quick registration system. In a few steps you will be able to register your data in an agile, simple and secure way, avoiding queues at reception. + If you register your data in our system, your passage through reception will be much faster, being able to enjoy the comfort of your room right away. + + + + +
+ + + + +
+ +
Check-in +
+
Hacer check-in
+
+
+
+
+
+
diff --git a/pms/data/pms_sequence.xml b/pms/data/pms_sequence.xml new file mode 100644 index 0000000000..f4b49d49c2 --- /dev/null +++ b/pms/data/pms_sequence.xml @@ -0,0 +1,16 @@ + + + + PMS Folio + pms.folio + F%(y)s + 5 + + + + PMS Checkin + pms.checkin.partner + %(y)s + 6 + + diff --git a/pms/data/queue_data.xml b/pms/data/queue_data.xml new file mode 100644 index 0000000000..8881324d02 --- /dev/null +++ b/pms/data/queue_data.xml @@ -0,0 +1,7 @@ + + + + autoinvoicing folios + + + diff --git a/pms/data/queue_job_function_data.xml b/pms/data/queue_job_function_data.xml new file mode 100644 index 0000000000..f1fba42330 --- /dev/null +++ b/pms/data/queue_job_function_data.xml @@ -0,0 +1,15 @@ + + + + + autoinvoice_folio + + + + + + autovalidate_folio_invoice + + + + diff --git a/pms/data/traveller_report_paperformat.xml b/pms/data/traveller_report_paperformat.xml new file mode 100644 index 0000000000..8e1a25b1d4 --- /dev/null +++ b/pms/data/traveller_report_paperformat.xml @@ -0,0 +1,18 @@ + + + + Traveller Report PaperFormat + + custom + 200 + 75 + Portrait + 1 + 3 + 0 + 0 + + 1 + 201 + + diff --git a/pms/demo/airbnb-logo.svg b/pms/demo/airbnb-logo.svg new file mode 100644 index 0000000000..4dfb99dc1e --- /dev/null +++ b/pms/demo/airbnb-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pms/demo/booking-logo.svg b/pms/demo/booking-logo.svg new file mode 100644 index 0000000000..ae95a2a2d8 --- /dev/null +++ b/pms/demo/booking-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pms/demo/pms_folio.xml b/pms/demo/pms_folio.xml new file mode 100644 index 0000000000..7f07f2951f --- /dev/null +++ b/pms/demo/pms_folio.xml @@ -0,0 +1,1058 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Each guest pays his bill + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + out + + + + + + + + + + + + + + + + + + + + + Do not allow guests to pay anything. The company pays for everything. + + + + + + + + + + + + + + + + + + + + + out + + + + + + + + + + + + + Each guest pays his bill + + + + + + + + + + + Each guest pays his bill + + + + + + + + + + + + + + + + + + + normal + + + + + + + + normal + + + + + + + + + + + normal + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Each guest pays his bill + + + + + + + + + + + Each guest pays his bill + + + + + + + + + + + Each guest pays his bill + + + + + + + + + + + + Each guest pays his bill + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pms/demo/pms_master_data.xml b/pms/demo/pms_master_data.xml new file mode 100644 index 0000000000..f628bb8f0e --- /dev/null +++ b/pms/demo/pms_master_data.xml @@ -0,0 +1,254 @@ + + + + + + + + 5 + + + + + + + 7 + + + + + + + 7 + + + + + + + 7 + + + + + + + 7 + + + + + + + 7 + + + + + + + 5 + + + + + + + 5 + + + + + + + 2 + + + + + + + 10 + 5 + + + + + + + 10 + + + + + + + 3 + + + + + + + 1 + + + + + + + 3 + + + + + + + 3 + 5 + + + + + + + 3 + + + + + + + 3 + + + + + + + 2 + 5 + + + + + + + + 2 + + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 3 + + + + + + + 2 + + + + + + + 3 + 5 + + + + + + + 3 + 5 + 3 + + + + + + + 2 + 5 + + + + + + + 2 + 5 + + + + + + + 2 + 5 + + + + + + + 2 + + + + + + + True + + + + + + + True + + + + + + + True + + + + diff --git a/pms/demo/pms_master_data_no_update.xml b/pms/demo/pms_master_data_no_update.xml new file mode 100644 index 0000000000..8fe01cd73e --- /dev/null +++ b/pms/demo/pms_master_data_no_update.xml @@ -0,0 +1,4675 @@ + + + + + Availability Plan Demo + + + Availability Plan Demo + + + + + + + + Alda Company + + + + + + False + + + + PMS Folio 2 + pms.folio + F/%(y)s + %(sec)s + 4 + + + + PMS Checkin 2 + pms.checkin.partner + C/%(y)s + %(sec)s + 4 + + + + + San Carlos + + + + + + + + + + + + + + + + + + + + + + + + Ground floor + + + First floor + + + Second floor + + + Third floor + + + Fourth floor + + + + Toiletries + + + Connectivity + + + Kitchen facilities + + + + Shampoo and Soap + + + + High-quality Shampoo and Soap Essential Herbs + + + + Hair Dryer + + + + High speed Wired Internet access + + + + Wi-Fi + + + + Microwave oven + + + + Half-sized Refrigerator + + + + + Room + RO + + + Conference + CO + + + Parking + PA + + + + Economic + ECO + 21.00 + + + + + Single + SNG + 20.00 + + + + + Double + DBL + 25.00 + + + + + Triple + TRP + 35.00 + + + + + Quadruple + QDR + 45.00 + + + + + Conference Room + CFR + 50.00 + + + + + Parking + PRK + 11.00 + + + + + + Grand suite + GRSUI + 100.00 + + + + + + Junior suite + JUSUI + 70.00 + + + + + + + Economic-101 + + + 2 + + + + Economic-102 + + + 2 + + + + Economic-103 + + + 2 + + + + Economic-104 + + + 2 + + + + Economic-105 + + + 2 + + + + + Single-106 + + + 1 + + + + Single-107 + + + 1 + + + + Single-108 + + + 1 + + + + Single-109 + + + 1 + + + + Single-110 + + + 1 + + + + + Double-201 + + + 2 + 1 + + + + Double-202 + + + 2 + + + + Double-203 + + + 2 + + + + Double-204 + + + 2 + + + + Double-205 + + + 2 + + + + + Triple-206 + + + 3 + + + + Triple-207 + + + 3 + + + + Triple-208 + + + 3 + + + + Triple-209 + + + 3 + + + + Triple-210 + + + 3 + + + + + Quadruple-301 + + + 4 + + + + Quadruple-302 + + + 4 + + + + Quadruple-303 + + + 4 + + + + Quadruple-304 + + + 4 + + + + Quadruple-305 + + + 4 + + + + + Open Talk Away Room 1 + + + 10 + + + + Open Talk Away Room 2 + + + 8 + + + + Open Talk Away Room 3 + + + 6 + + + + Open Talk Away Room 4 + + + 5 + + + + Open Talk Away Room 5 + + + 8 + + + + + Grand Suite-401 + + + 2 + + + + Grand Suite-402 + + + 2 + + + + Grand Suite-403 + + + 2 + + + + Grand Suite-404 + + + 2 + + + + Grand Suite-405 + + + 2 + + + + + Junior Suite-406 + + + 2 + + + + Junior Suite-407 + + + 2 + + + + Junior Suite-408 + + + 2 + + + + Junior Suite-409 + + + 2 + + + + Junior Suite-410 + + + 2 + + + + + Economic-101 + + + 2 + + + + Economic-102 + + + 2 + + + + Economic-103 + + + 2 + + + + Economic-104 + + + 2 + + + + Economic-105 + + + 2 + + + + + Double-201 + + + 2 + 1 + + + + Double-202 + + + 2 + + + + Double-203 + + + 2 + + + + Double-204 + + + 2 + + + + Double-205 + + + 2 + + + + + Triple-206 + + + 3 + + + + Triple-207 + + + 3 + + + + Triple-208 + + + 3 + + + + Triple-209 + + + 3 + + + + Triple-210 + + + 3 + + + + + Open Talk Away Room 1 + + + 10 + + + + Open Talk Away Room 2 + + + 8 + + + + Open Talk Away Room 3 + + + 6 + + + + Open Talk Away Room 4 + + + 5 + + + + Open Talk Away Room 5 + + + 8 + + + + + Parking-1 + + + 2 + + + + Parking-2 + + + 2 + + + + Parking-3 + + + 2 + + + + + Parking-1 + + + 2 + + + + Parking-2 + + + 2 + + + + Parking-3 + + + 2 + + + + + Breakfast Buffet + 5.0 + service + False + True + True + after + + + Extra Bed + 15.0 + service + False + True + False + 1 + True + + + Late Check-out + 10.0 + service + False + False + False + + + Lunch + 15.0 + service + False + True + True + + + Dinner + 20.0 + service + False + True + True + + + Free Bar + 40.0 + service + False + True + True + + + + BreakFast + BB + + + + Half Board + HB + + + + Full Board + FB + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Maintenance + + Used for closing rooms which requires a maintenance. You can specify + the reason in the own reservation. + + + + VIP Privacy + + Used for closing rooms for extra privacy. + + + + + True + + Booking.com + Herengracht 597 + Amsterdam + +31 20 712 5609 + + info@booking.com + http://www.booking.com + + + + True + + Airbnb.com + 8 Hanover Quay + Dublin + (+34) 91 123 45 67. + + info@airbnb.com + http://www.airbnb.com + + + + + 645773288 + + + 666777888 + + + 611567888 + + + 610067000 + + + 689017000 + + + 633773338 + + + 678112438 + + + + + + + 0_product_variant + list_price + fixed + + + + + 30.00 + + + + 0_product_variant + list_price + fixed + + + + + 27.00 + + + + 0_product_variant + list_price + fixed + + + + + 35.00 + + + + 0_product_variant + list_price + fixed + + + + + 29.00 + + + + 0_product_variant + list_price + fixed + + + + + 29.00 + + + + 0_product_variant + list_price + fixed + + + + + 31.00 + + + + 0_product_variant + list_price + fixed + + + + + 26.00 + + + + 0_product_variant + list_price + fixed + + + + + 26.00 + + + + 0_product_variant + list_price + fixed + + + + + 26.00 + + + + 0_product_variant + list_price + fixed + + + + + 24.00 + + + + 0_product_variant + list_price + fixed + + + + + 30.00 + + + + 0_product_variant + list_price + fixed + + + + + 35.00 + + + + 0_product_variant + list_price + fixed + + + + + 35.00 + + + + 0_product_variant + list_price + fixed + + + + + 35.00 + + + + 0_product_variant + list_price + fixed + + + + + 39.00 + + + + 0_product_variant + list_price + fixed + + + + + 35.00 + + + + 0_product_variant + list_price + fixed + + + + + 39.00 + + + + 0_product_variant + list_price + fixed + + + + + 40.00 + + + + 0_product_variant + list_price + fixed + + + + + 42.00 + + + + 0_product_variant + list_price + fixed + + + + + 38.00 + + + + 0_product_variant + list_price + fixed + + + + + 33.00 + + + + 0_product_variant + list_price + fixed + + + + + 33.00 + + + + 0_product_variant + list_price + fixed + + + + + 30.00 + + + + 0_product_variant + list_price + fixed + + + + + 30.00 + + + + 0_product_variant + list_price + fixed + + + + + 35.00 + + + + 0_product_variant + list_price + fixed + + + + + 39.00 + + + + 0_product_variant + list_price + fixed + + + + + 39.00 + + + + 0_product_variant + list_price + fixed + + + + + 33.00 + + + + 0_product_variant + list_price + fixed + + + + + 29.00 + + + + 0_product_variant + list_price + fixed + + + + + 27.00 + + + + 0_product_variant + list_price + fixed + + + + + 27.00 + + + + 0_product_variant + list_price + fixed + + + + + 32.00 + + + + + + 0_product_variant + list_price + fixed + + + + + 23.00 + + + + 0_product_variant + list_price + fixed + + + + + 26.00 + + + + 0_product_variant + list_price + fixed + + + + + 23.00 + + + + 0_product_variant + list_price + fixed + + + + + 23.00 + + + + 0_product_variant + list_price + fixed + + + + + 23.00 + + + + 0_product_variant + list_price + fixed + + + + + 25.00 + + + + 0_product_variant + list_price + fixed + + + + + 25.00 + + + + 0_product_variant + list_price + fixed + + + + + 25.00 + + + + 0_product_variant + list_price + fixed + + + + + 27.00 + + + + 0_product_variant + list_price + fixed + + + + + 28.00 + + + + 0_product_variant + list_price + fixed + + + + + 30.00 + + + + 0_product_variant + list_price + fixed + + + + + 30.00 + + + + 0_product_variant + list_price + fixed + + + + + 32.00 + + + + 0_product_variant + list_price + fixed + + + + + 32.00 + + + + 0_product_variant + list_price + fixed + + + + + 28.00 + + + + 0_product_variant + list_price + fixed + + + + + 25.00 + + + + 0_product_variant + list_price + fixed + + + + + 22.00 + + + + 0_product_variant + list_price + fixed + + + + + 22.00 + + + + 0_product_variant + list_price + fixed + + + + + 24.00 + + + + 0_product_variant + list_price + fixed + + + + + 30.00 + + + + 0_product_variant + list_price + fixed + + + + + 32.00 + + + + 0_product_variant + list_price + fixed + + + + + 35.00 + + + + 0_product_variant + list_price + fixed + + + + + 35.00 + + + + 0_product_variant + list_price + fixed + + + + + 27.00 + + + + 0_product_variant + list_price + fixed + + + + + 25.00 + + + + 0_product_variant + list_price + fixed + + + + + 25.00 + + + + 0_product_variant + list_price + fixed + + + + + 28.00 + + + + 0_product_variant + list_price + fixed + + + + + 30.00 + + + + 0_product_variant + list_price + fixed + + + + + 32.00 + + + + 0_product_variant + list_price + fixed + + + + + 32.00 + + + + 0_product_variant + list_price + fixed + + + + + 27.00 + + + + 0_product_variant + list_price + fixed + + + + + 25.00 + + + + + + 0_product_variant + list_price + fixed + + + + + 30.00 + + + + 0_product_variant + list_price + fixed + + + + + 32.00 + + + + 0_product_variant + list_price + fixed + + + + + 32.00 + + + + 0_product_variant + list_price + fixed + + + + + 35.00 + + + + 0_product_variant + list_price + fixed + + + + + 36.00 + + + + 0_product_variant + list_price + fixed + + + + + 38.00 + + + + 0_product_variant + list_price + fixed + + + + + 40.00 + + + + 0_product_variant + list_price + fixed + + + + + 43.00 + + + + 0_product_variant + list_price + fixed + + + + + 43.00 + + + + 0_product_variant + list_price + fixed + + + + + 45.00 + + + + 0_product_variant + list_price + fixed + + + + + 42.00 + + + + 0_product_variant + list_price + fixed + + + + + 42.00 + + + + 0_product_variant + list_price + fixed + + + + + 40.00 + + + + 0_product_variant + list_price + fixed + + + + + 40.00 + + + + 0_product_variant + list_price + fixed + + + + + 36.00 + + + + 0_product_variant + list_price + fixed + + + + + 32.00 + + + + 0_product_variant + list_price + fixed + + + + + 32.00 + + + + 0_product_variant + list_price + fixed + + + + + 30.00 + + + + 0_product_variant + list_price + fixed + + + + + 28.00 + + + + 0_product_variant + list_price + fixed + + + + + 28.00 + + + + 0_product_variant + list_price + fixed + + + + + 28.00 + + + + 0_product_variant + list_price + fixed + + + + + 34.00 + + + + 0_product_variant + list_price + fixed + + + + + 36.00 + + + + 0_product_variant + list_price + fixed + + + + + 38.00 + + + + 0_product_variant + list_price + fixed + + + + + 38.00 + + + + 0_product_variant + list_price + fixed + + + + + 42.00 + + + + 0_product_variant + list_price + fixed + + + + + 44.00 + + + + 0_product_variant + list_price + fixed + + + + + 44.00 + + + + 0_product_variant + list_price + fixed + + + + + 35.00 + + + + 0_product_variant + list_price + fixed + + + + + 33.00 + + + + 0_product_variant + list_price + fixed + + + + + 30.00 + + + + 0_product_variant + list_price + fixed + + + + + 30.00 + + + + + + 0_product_variant + list_price + fixed + + + + + 40.00 + + + + 0_product_variant + list_price + fixed + + + + + 44.00 + + + + 0_product_variant + list_price + fixed + + + + + 48.00 + + + + 0_product_variant + list_price + fixed + + + + + 50.00 + + + + 0_product_variant + list_price + fixed + + + + + 52.00 + + + + 0_product_variant + list_price + fixed + + + + + 52.00 + + + + 0_product_variant + list_price + fixed + + + + + 55.00 + + + + 0_product_variant + list_price + fixed + + + + + 50.00 + + + + 0_product_variant + list_price + fixed + + + + + 48.00 + + + + 0_product_variant + list_price + fixed + + + + + 48.00 + + + + 0_product_variant + list_price + fixed + + + + + 48.00 + + + + 0_product_variant + list_price + fixed + + + + + 54.00 + + + + 0_product_variant + list_price + fixed + + + + + 52.00 + + + + 0_product_variant + list_price + fixed + + + + + 50.00 + + + + 0_product_variant + list_price + fixed + + + + + 44.00 + + + + 0_product_variant + list_price + fixed + + + + + 40.00 + + + + 0_product_variant + list_price + fixed + + + + + 40.00 + + + + 0_product_variant + list_price + fixed + + + + + 38.00 + + + + 0_product_variant + list_price + fixed + + + + + 38.00 + + + + 0_product_variant + list_price + fixed + + + + + 38.00 + + + + 0_product_variant + list_price + fixed + + + + + 42.00 + + + + 0_product_variant + list_price + fixed + + + + + 45.00 + + + + 0_product_variant + list_price + fixed + + + + + 50.00 + + + + 0_product_variant + list_price + fixed + + + + + 52.00 + + + + 0_product_variant + list_price + fixed + + + + + 52.00 + + + + 0_product_variant + list_price + fixed + + + + + 48.00 + + + + 0_product_variant + list_price + fixed + + + + + 44.00 + + + + 0_product_variant + list_price + fixed + + + + + 44.00 + + + + 0_product_variant + list_price + fixed + + + + + 48.00 + + + + 0_product_variant + list_price + fixed + + + + + 50.00 + + + + 0_product_variant + list_price + fixed + + + + + 55.00 + + + + 0_product_variant + list_price + fixed + + + + + 50.00 + + + + + + 0_product_variant + list_price + fixed + + + + + 120.00 + + + + 0_product_variant + list_price + fixed + + + + + 124.00 + + + + 0_product_variant + list_price + fixed + + + + + 124.00 + + + + 0_product_variant + list_price + fixed + + + + + 120.00 + + + + 0_product_variant + list_price + fixed + + + + + 115.00 + + + + 0_product_variant + list_price + fixed + + + + + 118.00 + + + + 0_product_variant + list_price + fixed + + + + + 118.00 + + + + 0_product_variant + list_price + fixed + + + + + 125.00 + + + + 0_product_variant + list_price + fixed + + + + + 128.00 + + + + 0_product_variant + list_price + fixed + + + + + 130.00 + + + + 0_product_variant + list_price + fixed + + + + + 130.00 + + + + 0_product_variant + list_price + fixed + + + + + 125.00 + + + + 0_product_variant + list_price + fixed + + + + + 120.00 + + + + 0_product_variant + list_price + fixed + + + + + 115.00 + + + + 0_product_variant + list_price + fixed + + + + + 110.00 + + + + 0_product_variant + list_price + fixed + + + + + 110.00 + + + + 0_product_variant + list_price + fixed + + + + + 110.00 + + + + 0_product_variant + list_price + fixed + + + + + 120.00 + + + + 0_product_variant + list_price + fixed + + + + + 125.00 + + + + 0_product_variant + list_price + fixed + + + + + 130.00 + + + + 0_product_variant + list_price + fixed + + + + + 135.00 + + + + 0_product_variant + list_price + fixed + + + + + 125.00 + + + + 0_product_variant + list_price + fixed + + + + + 123.00 + + + + 0_product_variant + list_price + fixed + + + + + 123.00 + + + + 0_product_variant + list_price + fixed + + + + + 123.00 + + + + 0_product_variant + list_price + fixed + + + + + 126.00 + + + + 0_product_variant + list_price + fixed + + + + + 128.00 + + + + 0_product_variant + list_price + fixed + + + + + 135.00 + + + + 0_product_variant + list_price + fixed + + + + + 135.00 + + + + 0_product_variant + list_price + fixed + + + + + 127.00 + + + + 0_product_variant + list_price + fixed + + + + + 122.00 + + + + 0_product_variant + list_price + fixed + + + + + 120.00 + + + + + Public Pricelist Discount 10% + + + + + + + + 3_global + pricelist + + formula + 10.00 + + + + + + + + + 3 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 3 + + + + + + + 3 + + + + + + + 3 + + + + + + + 2 + 1 + + + + + + + 1 + + + + + + + 1 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 1 + + + + + + + 1 + + + + + + + 1 + + + + + + + 1 + + + + + + + 3 + + + + + + + 3 + + + + + + + 1 + + + + + + + 1 + + + + + + + 1 + + + + + + + 3 + + + + + + + 2 + + + + + + + 3 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 3 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + + + 3 + 5 + + + + + + + 2 + 5 + + + + + + + 2 + 5 + + + + + + + 2 + + + + + + + 2 + 5 + + + + + + + 3 + + + + + + + 2 + + + + + + + 2 + 10 + + + + + + + 5 + + + + + + + 5 + 3 + + + + + + + 5 + 2 + + + + + + + 1 + + + + + + + 2 + 1 + + + + + + + 2 + 1 + + + + + + + 1 + + + + + + + + + + + + + 2 + 3 + + + + + + + 3 + + + + + + + 2 + 1 + + + + + + + 2 + 1 + + + + + + + 2 + 1 + + + + + + + 3 + 5 + + + + + + + 3 + 5 + + + + + + + 3 + 5 + + + + + + + 2 + 5 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + 4 + + + + + + + 3 + 1 + + + + + + + 3 + 1 + + + + + + + 3 + 1 + + + + + + + 3 + 1 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + + + + + + + 2 + 1 + + + + + + + 2 + 1 + + + + + + + 2 + 1 + + + + + + + True + + + + + + + True + + + + + + + True + + + + + + + True + + + + + + + True + + + + + + + True + + + + + + + 2 + 5 + 3 + + + + + + + 2 + 5 + 10 + + + + + + + 10 + 5 + + + + + + + 3 + + + + + + + 2 + + + + + + + 2 + + + + + + + 3 + + + diff --git a/pms/demo/pms_reservation.xml b/pms/demo/pms_reservation.xml new file mode 100644 index 0000000000..acc6fece84 --- /dev/null +++ b/pms/demo/pms_reservation.xml @@ -0,0 +1,1458 @@ + + + + + + + + out + + + + + Extra privacy for school field trip. + + + + + + + out + + + + + Carpet replacement. + + + + + + + out + + + + + Carpet replacement. + + + + + + + + out + + + + + Wall painting. + + + + + + + + + + + 1 + + + + + + done + Lactose intolerant, soy milk for breakfast. + + + + + + + onboard + + + + + + + + + + + 1 + + + + + + done + + + + + + + onboard + + + + + + + + + + + + + 1 + + + + + + done + Breakfast at 5:00 am. + + + + + + + + + onboard + + + + + + + + + + + + + + + 1 + + + + + + done + + + + + + + onboard + + + + + + + + + + + 1 + + + + + + done + + + + + + + onboard + + + + + + + + + + + 1 + + + + + + done + Need lunchs to take away. + + + + + + + onboard + + + + + + + + + + + 2 + onboard + + + + + + + + + + + + + + + + + + + Need 1 extra towel. + + + + + + + + + + + + + + + + + + + + + Need 2 extra towel. + + + + + + + + + + + + + + + + + + 2 + + + + + Need 2 extra towels. + Friend of manager + + The guests assume the splitted reservation because they want to stay the first + nights in room 105. + + + + + + + + + 1 + + + + + + + + + + + + 2 + + + + + + Preferably street view. + + + + + + + + 1 + + + + + + + + + + + + + 1 + + + + + + done + Late for dinner. Cold dinner needed. + + + + + + + onboard + + + + + + + + + + + 1 + + + + + + done + + + + + + + onboard + + + + + + + + + + + 1 + + + + + done + + + + + + + + onboard + + + + + + + + + + + 1 + + + + + + + breakfast at 5:00 am + + + + + + + + 1 + + + + + + + + + + + + + + + 1 + + + + + + Breakfast to take away. + + + + + + + + 1 + + + + + + + + + + + + + + 1 + + + + + + preferably street view + + + + + + + + 1 + + + + + + + + + + + + 1 + 1 + + + + + + Preferably street view. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + + + + + 1 bottle of champagne upon check-in + + + + + + + + 2 + + + + + + + + + + + + 2 + + + + + + + 3 extra towels + + + + + + + + 2 + + + + + + + + + + + + 2 + + + + + + + Vegan breakfast + + + + + + + + 2 + + + + + + + + + + + + + + 2 + + + + + + Help needed with the luggage. + + + + + + + + 1 + 1 + + + + + + + + + + + + + 1 + 1 + + + + + Need 1 extra set of bedclothes + + + + + + + + 2 + + + + + + + + + + + + + 2 + + + + + + welcome cocktail + + + + + + + + + 3 + + + + + + done + + + + + + + onboard + + + + + + + + + + + 2 + 1 + onboard + + + + + + + + Vegetarian food for all guests. + + + + + + + + + + + + + + + + + + + 3 + + + + + + 3 set of keys needed. + + + + + + + + 3 + + + + + + + + + + + + + 1 + + 2 + + + + + + Celiac breakfast + + + + + + + + 3 + + + + + + + + + + + + + + + + + room with low noise 24x7 + + + + + + + + + + + + done + + + + + + + onboard + + + + + + + + + + + + + + + done + Italian guests, they don't speak english + + + + + + + onboard + + + + + + + + + + + + + + done + + + + + + + onboard + + + + + + + + + + + + + + + Allergic to chemicals + done + + + + + + + onboard + + + + + + + + + + + + + + + + + + + + + + + + + Hard mattress needed. + + + + + + + + + + + + + + + + + + + + + + True + 0 + paid + + + done + 2 bottles of water each 2 hours. + + + + + + + onboard + + + + + + + + + + + + + + + + + + + + + + + + + + + Projector needed + + + + + + + + + + + + + + + + + + + + + + + + done + + + + + + + onboard + + + + + + + + + + + + + + Need 1 extra shampoo + done + + + + + + + onboard + + + + + + + + + + + + + + done + + + + + + + onboard + + + + + + + + + + + + + + Welcome cocktail. + + + + + + + + + + + + + + + + + + + + + Basket fruit at bedroom + + + + + + + + + + + + done + + + + + + + onboard + + + + + + + + + + + + + + allergic to chemicals + done + + + + + + + onboard + + + + + + + + + + + + + + done + + + + + + + onboard + + + + + + + + + + + + + + Smoking room. + + + + + + + + + + + + + + + + + + + + + two bottles of moët + + diff --git a/pms/i18n/es.po b/pms/i18n/es.po new file mode 100644 index 0000000000..6af909d984 --- /dev/null +++ b/pms/i18n/es.po @@ -0,0 +1,14172 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pms +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2021-09-12 14:11+0000\n" +"PO-Revision-Date: 2024-05-02 19:36+0000\n" +"Last-Translator: Darío Lodeiros \n" +"Language-Team: \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "" +"\n" +" Compute error: The first room line date should\n" +" be the same as the checkin date!\n" +" " +msgstr "" +"\n" +" Error de cálculo: La fecha de la primera línea de " +"habitación debería\n" +" ser la misma que la fecha de entrada.\n" +" " + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "" +"\n" +" Compute error: The last room line date should\n" +" be the previous day of the checkout date!\n" +" " +msgstr "" +"\n" +" Error de cálculo: La fecha de la última línea de " +"habitación debería\n" +" ¡ser el día anterior a la fecha de salida!\n" +" " + +#. module: pms +#: code:addons/pms/models/res_partner.py:0 +#, python-format +msgid "" +"\n" +" Agency must have a PMS pricelist, please review the\n" +" pricelists configuration (%s) to allow it for PMS,\n" +" or the pricelist selected for the agencies: %s\n" +" " +msgstr "" +"\n" +" Agencia debe tener una lista de precios PMS, por favor " +"revise la\n" +" configuración de listas de precios (%s) para permitirla para PMS,\n" +" o la lista de precios seleccionada para las agencias: %s\n" +" " + +#. module: pms +#: model:room.closure.reason,description:pms.pms_room_closure_reason_vip_privacy +msgid "" +"\n" +" Used for closing rooms for extra privacy.\n" +" " +msgstr "" +"\n" +" Se utiliza para cerrar habitaciones para mayor privacidad.\n" +" " + +#. module: pms +#: model:room.closure.reason,description:pms.pms_room_closure_reason_maintenance +msgid "" +"\n" +" Used for closing rooms which requires a maintenance. You can " +"specify\n" +" the reason in the own reservation.\n" +" " +msgstr "" +"\n" +" Se utiliza para cerrar salas que requieren un mantenimiento. " +"Puede especificar\n" +" el motivo en la propia reserva.\n" +" " + +#. module: pms +#: model:ir.model.fields,help:pms.field_res_company__pms_invoice_downpayment_policy +msgid "" +"\n" +" - Manual: Downpayment invoice will be created manually\n" +" - All: Downpayment invoice will be created automatically\n" +" - Current Month: Downpayment invoice will be created " +"automatically\n" +" only for reservations with checkout date past of current " +"month\n" +" " +msgstr "" +"\n" +" - Manual: La factura de anticipo se creará manualmente\n" +" - Todas: La factura de anticipo se creará automáticamente\n" +" - Mes en curso: La factura de anticipo se creará automáticamente\n" +" sólo para reservas con fecha de pago posterior al mes en curso\n" +" " + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__product_variant_count +msgid "# Product Variants" +msgstr "Variantes del Producto" + +#. module: pms +#: model:mail.template,subject:pms.confirmed_reservation_email +msgid "" +"${object.company_id.name} has confirmed your reservation in ${object." +"pms_property_id.name}" +msgstr "" +"${object.company_id.name} ha confirmado su reserva en ${object." +"pms_property_id.name}" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__penalty_late +msgid "% Penalty Late" +msgstr "% Penalización por cancelación fuera de tiempo" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__penalty_noshow +msgid "% Penalty No Show" +msgstr "% Penalización No Show" + +#. module: pms +#: code:addons/pms/models/pms_checkin_partner.py:0 +#, python-format +msgid "%s is not a valid %s identifier" +msgstr "%s no es un %s identificador válido" + +#. module: pms +#: code:addons/pms/models/pms_service_line.py:0 +#, python-format +msgid "%s limit exceeded for %s" +msgstr "%s límite excedido por %s" + +#. module: pms +#: code:addons/pms/models/pms_checkin_partner.py:0 +#, python-format +msgid "%s not found in checkins (%s)" +msgstr "%s no encontrado en los checkins (%s)" + +#. module: pms +#: code:addons/pms/models/pms_reservation_line.py:0 +#, python-format +msgid "%s: No room available in %s <-> %s." +msgstr "%s: No hay habitaciones disponibles en %s <-> %s." + +#. module: pms +#: code:addons/pms/models/pms_reservation_line.py:0 +#, python-format +msgid "%s: No room type available" +msgstr "%s: Tipo de habitación no disponible" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.report_folio_document +msgid "&nbsp;on&nbsp;" +msgstr "&nbsp;en&nbsp;" + +#. module: pms +#: model:ir.actions.report,print_report_name:pms.action_report_pms_pro_forma_invoice +msgid "'PRO-FORMA - %s' % (object.name)" +msgstr "'PRO-FORMA - %s' % (object.name)" + +#. module: pms +#: model:ir.actions.report,print_report_name:pms.action_report_folio +#: model:ir.actions.report,print_report_name:pms.action_traveller_report +msgid "" +"(object.state in ('draft', 'sent') and 'Quotation - %s' % (object.name)) or " +"'Order - %s' % (object.name)" +msgstr "" +"(object.state in ('draft', 'sent') and 'Quotation - %s' % (object.name)) or " +"'Order - %s' % (object.name)" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.report_folio_document +msgid ") Nights" +msgstr ") Noches" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid ", at" +msgstr ", a" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation_precheckin +msgid "" +".
\n" +" Do your check-in now and save time." +msgstr "" +"
\n" +" Haga su check-in ahora y ahorre tiempo." + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "" +"\n" +"
\n" +" Categoría:" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +msgid "Days:" +msgstr "Días:" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +msgid "Rules to apply:" +msgstr "Reglas a aplicar:" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation_precheckin +msgid "" +"
\n" +" This is our quick registration system. " +"In a few steps you will be able to register your data in an agile, simple " +"and secure way, avoiding queues at reception.\n" +" If you register your data in our system, your " +"passage through reception will be much faster, being able to enjoy " +"the comfort of your room right away." +msgstr "" +"
\n" +" Este es nuestrosistema de registro rápido. En pocos pasos podrás registrar tus datos de forma ágil, sencilla y " +"segura,evitando colas en recepción.\n" +" Si registras tus datos en nuestro sistema, tu " +"paso por recepción será mucho más rápido, pudiendo disfrutar al " +"instante de la comodidad de tu habitación." + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_not_checkin +msgid "
Email:" +msgstr "
Correo electrónico:" + +#. module: pms +#: model:mail.template,body_html:pms.precheckin_invitation_email +msgid "" +"
\n" +" Do your check-in now and save time.\n" +"
\n" +" Access our quick registration system. " +"In a few steps you will be able to register your data in an agile, simple " +"and secure way, avoiding queues at reception.\n" +" If you register your data in our system, your " +"passage through reception will be much faster, being able to enjoy " +"the comfort of your room right away.\n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +"
Check-in\n" +"
\n" +"
\"Hacer\n" +"
\n" +"
\n" +"
\n" +" " +msgstr "" +"
\n" +" Haz tu check-in ahora y ahorra tiempo.\n" +"
\n" +" Accede a nuestrosistema de registro rápido. En pocos pasos podrás registrar tus datos de forma ágil, sencilla y " +"segura,evitando colas en recepción.\n" +" Si registras tus datos en nuestro sistema, tu " +"paso por recepción será mucho más rápido, pudiendo disfrutar al " +"instante de la comodidad de tu habitación.\n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +"
Check-in\n" +"
\n" +"
\"Hacer\n" +"
\n" +"
\n" +"
\n" +" " + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folio_invitations +msgid "" +"\n" +" Room Type:\n" +" " +msgstr "" +"\n" +" Tipo de Habitación:\n" +" " + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation +msgid "" +" Reservations by folio: " +msgstr "" +" Reservas por ficha: " + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid " Cancelled Reservation!" +msgstr " Reserva Cancelada!" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.folio_portal_template +msgid " Download" +msgstr " Descargar" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_folio_page_payment +msgid " Pay Now" +msgstr " Paga Ahora" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_folio_page_payment +msgid " Paid" +msgstr " Pagado" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.folio_portal_template +msgid "Send message" +msgstr "Enviar mensaje" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "" +"" +msgstr "" +"" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.folio_portal_template +msgid " Print" +msgstr " Imprimir" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid " OverBooking" +msgstr " OverBooking" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid " Reselling" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation_detail +msgid "Status:" +msgstr "Estado:" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.booking_engine +msgid "" +"\n" +" Your reservation in ${object." +"pms_property_id.name} has been modified\n" +" \n" +"
\n" +" See you soon,
\n" +" \n" +"
\n" +" ${object.company_id." +"name}\n" +"
\n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +"
Reservation Details
\n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +"
\n" +"
\n" +"
From ${object.first_checkin}
\n" +"
To ${object.last_checkout}
\n" +"
TZ ${object.pms_property_id.tz}" +"
\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
Rooms: " +"
\n" +" % for " +"reservation in object.reservation_ids:\n" +" ${reservation." +"room_type_id.name}
\n" +" % endfor\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
Price: " +" ${object.amount_total} ${object.pms_property_id.country_id." +"currency_id.symbol}
\n" +"
\n" +"
\n" +"
\n" +" % if object.pms_property_id." +"mail_information\n" +"
\n" +"
Additional Information\n" +"

${object.pms_property_id." +"mail_information|safe}

\n" +" % endif\n" +"
\n" +"
\n" +"
\n" +" \n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" % if object.pms_property_id.privacy_policy\n" +" \n" +" \n" +"
\n" +" ${object.pms_property_id.privacy_policy|" +"safe}\n" +"
\n" +" % endif\n" +" \n" +" \n" +" % if object.company_id\n" +" \n" +" \n" +"
\n" +" Sent by ${object.company_id.name}\n" +"
\n" +"
\n" +" % endif\n" +" \n" +" \n" +" " +msgstr "" +"\n" +" \n" +" \n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +" \n" +" \n" +"
\n" +"
\n" +" % if object.pms_property_id." +"partner_id.street\n" +"

${object.pms_property_id." +"partner_id.street}

\n" +" % endif\n" +" % if object.pms_property_id." +"partner_id.street2\n" +"

${object.pms_property_id." +"partner_id.street2}

\n" +" % endif\n" +"

${object.pms_property_id." +"partner_id.zip}

\n" +"

${object.pms_property_id." +"partner_id.city}

\n" +"

${object.pms_property_id." +"partner_id.country_id.name}

\n" +"
\n" +"
\n" +"
\n" +"
\n" +" Hola ${object.partner_id." +"name or ''},
\n" +" Su reserva en${object." +"pms_property_id.name} has been modified\n" +"
\n" +"
\n" +" Nos vemos pronto,
\n" +" \n" +"
\n" +" ${object.company_id." +"name}\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
Reservation Details
\n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +"
\n" +"
\n" +"
From ${object.first_checkin}
\n" +"
To ${object.last_checkout}
\n" +"
TZ ${object.pms_property_id.tz}" +"
\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
Habitaciones: " +"
\n" +" % for " +"reservation in object.reservation_ids:\n" +" ${reservation." +"room_type_id.name}
\n" +" % endfor\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
Precio: " +" ${object.amount_total} ${object.pms_property_id.country_id." +"currency_id.symbol}
\n" +"
\n" +"
\n" +"
\n" +" % if object.pms_property_id." +"mail_information\n" +"
\n" +"
Información adicional
\n" +"

${object.pms_property_id." +"mail_information|safe}

\n" +" % endif\n" +"
\n" +"
\n" +"
\n" +" \n" +" \n" +"
\n" +"
\n" +"
\n" +" % if object.pms_property_id.privacy_policy\n" +" \n" +" \n" +"
\n" +" ${object.pms_property_id.privacy_policy|" +"safe}\n" +"
\n" +" % endif\n" +"
\n" +" % if object.company_id\n" +" \n" +" \n" +"
\n" +" Sent by ${object.company_id.name}\n" +"
\n" +"
\n" +" % endif\n" +"
\n" +" " + +#. module: pms +#: model:mail.template,body_html:pms.confirmed_reservation_email +msgid "" +"\n" +" \n" +" \n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +" \n" +" \n" +"
\n" +"
\n" +" % if object.pms_property_id." +"partner_id.street\n" +"

${object.pms_property_id." +"partner_id.street}

\n" +" % endif\n" +" % if object.pms_property_id." +"partner_id.street2\n" +"

${object.pms_property_id." +"partner_id.street2}

\n" +" % endif\n" +"

${object.pms_property_id." +"partner_id.zip}

\n" +"

${object.pms_property_id." +"partner_id.city}

\n" +"

${object.pms_property_id." +"partner_id.country_id.name}

\n" +"
\n" +"
\n" +"
\n" +"
\n" +" Hello ${object.partner_id." +"name or ''},
\n" +" We are happy to confirm your " +"reservation in ${object.pms_property_id.name}\n" +"
\n" +"
\n" +" See you soon,
\n" +" \n" +"
\n" +" ${object.company_id." +"name}\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
Reservation Details
\n" +" \n" +" \n" +"\n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +"
\n" +"
\n" +"
From ${object.first_checkin}
\n" +"
To ${object.last_checkout}
\n" +"
TZ ${object.pms_property_id.tz}" +"
\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
Rooms: " +"
\n" +" % for " +"reservation in object.reservation_ids:\n" +" ${reservation." +"room_type_id.name}
\n" +" % endfor\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
Price: " +" ${object.amount_total} ${object.pms_property_id.country_id." +"currency_id.symbol}
\n" +"
\n" +"
\n" +"
\n" +" % if object.pms_property_id." +"mail_information\n" +"
\n" +"
Additional Information\n" +" ${object.pms_property_id." +"mail_information|safe}\n" +" % endif\n" +"
\n" +"
\n" +"
\n" +"
\n" +" Do your check-in now and " +"save time.\n" +"
\n" +" Access our quick " +"registration system. In a few steps you will be able to register " +"your data in an agile, simple and secure way, avoiding queues at " +"reception.\n" +" If you register your " +"data in our system, your passage through reception will be much " +"faster, being able to enjoy the comfort of your room right away.\n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +"
Check-in\n" +"
\n" +"

+\n" +"
\n" +"
\n" +"
\n" +"
\n" +" \n" +" \n" +"
\n" +"
\n" +"
\n" +" % if object.pms_property_id.privacy_policy\n" +" \n" +" \n" +"
\n" +" ${object.pms_property_id.privacy_policy|" +"safe}\n" +"
\n" +" % endif\n" +"
\n" +" % if object.company_id\n" +" \n" +" \n" +"
\n" +" Sent by ${object.company_id.name}\n" +"
\n" +"
\n" +" % endif\n" +"
\n" +" " +msgstr "" +"\n" +" \n" +" \n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +" \n" +" \n" +"
\n" +"
\n" +" % if object.pms_property_id." +"partner_id.street\n" +"

${object.pms_property_id." +"partner_id.street}

\n" +" % endif\n" +" % if object.pms_property_id." +"partner_id.street2\n" +"

${object.pms_property_id." +"partner_id.street2}

\n" +" % endif\n" +"

${object.pms_property_id." +"partner_id.zip}

\n" +"

${object.pms_property_id." +"partner_id.city}

\n" +"

${object.pms_property_id." +"partner_id.country_id.name}

\n" +"
\n" +"
\n" +"
\n" +"
\n" +" Hola ${object.partner_id." +"name or ''},
\n" +" Estamos muy felicoes de " +"confirmar su registro en ${object.pms_property_id.name}\n" +"
\n" +"
\n" +" Nos vemos pronto,
\n" +" \n" +"
\n" +" ${object.company_id." +"name}\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
Reservation Details
\n" +" \n" +" \n" +"\n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +"
\n" +"
\n" +"
Desde ${object.first_checkin}
\n" +"
Hasta ${object.last_checkout}
\n" +"
TZ ${object.pms_property_id.tz}" +"
\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
Habitaciones: " +"
\n" +" % para reserva " +"en object.reservation_ids:\n" +" ${reservation." +"room_type_id.name}
\n" +" % final para\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
Precio: " +" ${object.amount_total} ${object.pms_property_id.country_id." +"currency_id.symbol}
\n" +"
\n" +"
\n" +"
\n" +" % if object.pms_property_id." +"mail_information\n" +"
\n" +"
Información Adicional
\n" +" ${object.pms_property_id." +"mail_information|safe}\n" +" % endif\n" +"
\n" +"
\n" +"
\n" +"
\n" +" Haga su registro ahora y " +"ahorre tiempo.\n" +"
\n" +"Accede a nuestrosistema de registro rápido. En pocos pasos " +"podrás registrar tus datos de forma ágil, sencilla y segura,evitando " +"colas en recepción.\n" +" Si registras tus datos " +"en nuestro sistema, tu paso por recepción será mucho más rápido, pudiendo disfrutar al instante de la comodidad de tu habitación.\n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +"
Check-in\n" +"
\n" +"

+\n" +"
\n" +"
\n" +"
\n" +"
\n" +" \n" +" \n" +"
\n" +"
\n" +"
\n" +" % if object.pms_property_id.privacy_policy\n" +" \n" +" \n" +"
\n" +" ${object.pms_property_id.privacy_policy|" +"safe}\n" +"
\n" +" % endif\n" +"
\n" +" % if object.company_id\n" +" \n" +" \n" +"
\n" +" Sent by ${object.company_id.name}\n" +"
\n" +"
\n" +" % endif\n" +"
\n" +" " + +#. module: pms +#: model:mail.template,body_html:pms.cancelled_reservation_email +msgid "" +"\n" +" \n" +" \n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +" \n" +" \n" +"
\n" +"
\n" +" % if object.pms_property_id." +"partner_id.street\n" +"

${object.pms_property_id." +"partner_id.street}

\n" +" % endif\n" +" % if object.pms_property_id." +"partner_id.street2\n" +"

${object.pms_property_id." +"partner_id.street2}

\n" +" % endif\n" +"

${object.pms_property_id." +"partner_id.zip}

\n" +"

${object.pms_property_id." +"partner_id.city}

\n" +"

${object.pms_property_id." +"partner_id.country_id.name}

\n" +"
\n" +"
\n" +"
\n" +"
\n" +" Hello ${object.partner_id." +"name or ''},
\n" +" Your reservation at ${object." +"pms_property_id.name} has been cancelled.\n" +"
\n" +"
\n" +"
\n" +" \n" +" \n" +"
\n" +"
\n" +" \n" +" \n" +"
\n" +"
\n" +"
\n" +" % if object.pms_property_id.privacy_policy\n" +" \n" +" \n" +"
\n" +" ${object.pms_property_id.privacy_policy}\n" +"
\n" +" % endif\n" +"
\n" +" % if object.company_id\n" +" \n" +" \n" +"
\n" +" Sent by ${object.company_id.name}\n" +"
\n" +"
\n" +" % endif\n" +"
\n" +" " +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "A closure reason is mandatory when reservation type is 'out of service'" +msgstr "" +"El motivo del cierre es obligatorio cuando el tipo de reserva es 'fuera de " +"servicio'" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "A customer/s has this email or mobile, do you want to add it?" +msgstr "Un cliente/s tiene este email o móvil, ¿quiere añadirlo?" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room__description_sale +msgid "" +"A description of the Product that you want to communicate to your " +"customers. This description will be copied to every Sales Order, Delivery " +"Order and Customer Invoice/Credit Note" +msgstr "" +"Descripción del producto que quiere comunicarle a sus clientes. Esta " +"descripción será copiada en todas las órdenes de venta, órdenes de entrega y " +"Facturas del Cliente" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__description_sale +msgid "" +"A description of the Product that you want to communicate to your customers. " +"This description will be copied to every Sales Order, Delivery Order and " +"Customer Invoice/Credit Note" +msgstr "" +"Descripción del Producto que quiere comunicarle a sus clientes. La " +"descripción será copiada a todas las Órdenes de Venta, Órdenes de Entrega y " +"Facturas del Cliente" + +#. module: pms +#: code:addons/pms/models/res_partner.py:0 +#, python-format +msgid "A document identification is required" +msgstr "Se requiere un documento de identificación" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "A journal must be specified for the acquirer %s." +msgstr "Debe especificarse un diario para la entidad adquirente %s." + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "A payment acquirer is required to create a transaction." +msgstr "Se requiere un adquirente de pagos para crear una transacción." + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_advance_payment_inv__advance_payment_method +msgid "" +"A standard invoice is issued with all the order lines ready " +"for invoicing, according to their invoicing " +"policy (based on ordered or delivered quantity)." +msgstr "" +"Se emite una factura estándar con todas las líneas del pedido listas para " +"facturar, de acuerdo con su política de facturación (en función de la " +"cantidad pedida o entregada)." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__type +msgid "" +"A storable product is a product for which you manage stock. The Inventory " +"app has to be installed.\n" +"A consumable product is a product for which stock is not managed.\n" +"A service is a non-material product you provide." +msgstr "" +"Un producto almacenado es un producto para el que se gestiona su stock. La " +"aplicación del inventario debe estar instalada.\n" +"Un producto consumible es un producto cuyo stock no se gestiona.\n" +"Un servicio es un producto no material que proporciona." + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "A transaction can't be linked to folios having different currencies." +msgstr "" +"Una transacción no puede vincularse a folios que tengan divisas diferentes." + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "A transaction can't be linked to folios having different partners." +msgstr "" +"Una transacción no se puede vincular a folios que tengan diferentes socios." + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__aeat_anonymous_cash_customer +msgid "AEAT - Anonymous customer" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__aeat_identification_type +msgid "AEAT Identification type" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__user_ids +msgid "Accepted Users" +msgstr "Usuarios Aceptados" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__access_warning +#: model:ir.model.fields,field_description:pms.field_pms_folio__access_warning +#: model:ir.model.fields,field_description:pms.field_pms_reservation__access_warning +msgid "Access warning" +msgstr "Aviso de Acceso" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__property_account_payable_id +msgid "Account Payable" +msgstr "Cuenta por Pagar" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__property_account_receivable_id +msgid "Account Receivable" +msgstr "Cuenta por Cobrar" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_advance_payment_inv__deposit_account_id +msgid "Account used for deposits" +msgstr "Cuenta Utilizada para Depósitos" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__message_needaction +#: model:ir.model.fields,field_description:pms.field_pms_folio__message_needaction +#: model:ir.model.fields,field_description:pms.field_pms_property__message_needaction +#: model:ir.model.fields,field_description:pms.field_pms_reservation__message_needaction +#: model:ir.model.fields,field_description:pms.field_pms_room_type__message_needaction +msgid "Action Needed" +msgstr "Acción Necesaria" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_amenity__active +#: model:ir.model.fields,field_description:pms.field_pms_amenity_type__active +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan__active +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__active +#: model:ir.model.fields,field_description:pms.field_pms_property__active +#: model:ir.model.fields,field_description:pms.field_pms_room__active +#: model:ir.model.fields,field_description:pms.field_pms_room_type__active +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class__active +#: model:ir.model.fields,field_description:pms.field_pms_team_member__active +msgid "Active" +msgstr "Activo" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__active_lang_count +msgid "Active Lang Count" +msgstr "Recuento de Idiomas Activos" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__activity_ids +#: model:ir.model.fields,field_description:pms.field_pms_folio__activity_ids +#: model:ir.model.fields,field_description:pms.field_pms_property__activity_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__activity_ids +#: model:ir.model.fields,field_description:pms.field_pms_room_type__activity_ids +msgid "Activities" +msgstr "Actividades" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__activity_exception_decoration +#: model:ir.model.fields,field_description:pms.field_pms_folio__activity_exception_decoration +#: model:ir.model.fields,field_description:pms.field_pms_property__activity_exception_decoration +#: model:ir.model.fields,field_description:pms.field_pms_reservation__activity_exception_decoration +#: model:ir.model.fields,field_description:pms.field_pms_room_type__activity_exception_decoration +msgid "Activity Exception Decoration" +msgstr "Decoración de Actividad de Excepción" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__activity_state +#: model:ir.model.fields,field_description:pms.field_pms_folio__activity_state +#: model:ir.model.fields,field_description:pms.field_pms_property__activity_state +#: model:ir.model.fields,field_description:pms.field_pms_reservation__activity_state +#: model:ir.model.fields,field_description:pms.field_pms_room_type__activity_state +msgid "Activity State" +msgstr "Estado de la actividad" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__activity_type_icon +#: model:ir.model.fields,field_description:pms.field_pms_folio__activity_type_icon +#: model:ir.model.fields,field_description:pms.field_pms_property__activity_type_icon +#: model:ir.model.fields,field_description:pms.field_pms_reservation__activity_type_icon +#: model:ir.model.fields,field_description:pms.field_pms_room_type__activity_type_icon +msgid "Activity Type Icon" +msgstr "Icono para el Tipo de Actividad" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_board_service +msgid "Add Board Service to reservations" +msgstr "Añadir servicio de mesa a las reservas" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.several_partners_wizard +msgid "Add Customer" +msgstr "Añadir cliente" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_service +msgid "Add Service to reservations" +msgstr "Añadir servicio a las reservas" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Add a note" +msgstr "Añadir Una Nota" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Add a product" +msgstr "Añadir Un Producto" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Add a section" +msgstr "Añadir Una Sección" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Add customer" +msgstr "Añadir cliente" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_amenity__is_add_code_room_name +msgid "Add in room name" +msgstr "Añadir al nombre de la habitación" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.booking_engine +msgid "Add to Folio" +msgstr "Añadir a la Ficha" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__mail_information +msgid "Additional information of the mail" +msgstr "Información adicional sobre el correo" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__type +msgid "Address Type" +msgstr "Tipo de Dirección" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Aditional Mail Information" +msgstr "Información postal adicional" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_team_member__pms_role__administrative +msgid "Administrative" +msgstr "Administrativo" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__administrative_user_id +msgid "Administrative Manager" +msgstr "Director Administrativo" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_board_service_line__adults +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type_line__adults +#: model:ir.model.fields,field_description:pms.field_pms_reservation__adults +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__adults +msgid "Adults" +msgstr "Adultos" + +#. module: pms +#: code:addons/pms/models/pms_board_service_line.py:0 +#: code:addons/pms/models/pms_board_service_room_type_line.py:0 +#, python-format +msgid "Adults or Children must be checked" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_advanced_filters_wizard +#: model:ir.ui.menu,name:pms.menu_pms_advanced_filters +msgid "Advanced Filters" +msgstr "Filtros Avanzados" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.advanced_filters_wizard +msgid "Advanced filters" +msgstr "Filtros Avanzados" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__aeat_identification +msgid "Aeat Identification" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__product_template__consumed_on__after +msgid "After night" +msgstr "Después de la noche" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__age +msgid "Age" +msgstr "Edad" + +#. module: pms +#: model:ir.actions.act_window,name:pms.pms_agency_action +#: model:ir.ui.menu,name:pms.pms_agency_menu +msgid "Agencies" +msgstr "Agencias" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__agency_id +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__agency_id +#: model:ir.model.fields,field_description:pms.field_pms_folio__agency_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__agency_id +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +#: model_terms:ir.ui.view,arch_db:pms.res_partner_view_form +msgid "Agency" +msgstr "Agencia" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__agency_id +#: model:ir.model.fields,help:pms.field_pms_booking_engine__agency_id +#: model:ir.model.fields,help:pms.field_pms_reservation__agency_id +msgid "Agency that made the reservation" +msgstr "Agencia que hizo la reserva" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__res_company__pms_invoice_downpayment_policy__all +msgid "All" +msgstr "Todos/as" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_cancelation_rule__apply_on_late__all +#: model:ir.model.fields.selection,name:pms.selection__pms_cancelation_rule__apply_on_noshow__all +msgid "All Days" +msgstr "Todos los Días" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.wizard_folio_changes_view_form +msgid "All days" +msgstr "Todos los Días" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__lang +msgid "" +"All the emails and documents sent to this contact will be translated in this " +"language." +msgstr "" +"Todos los emails y documentos enviados a este contacto van a ser traducidos " +"a este idioma." + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__allowed_method_ids +msgid "Allowed Method" +msgstr "Método Permitido" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.res_users_view_form +msgid "Allowed Properties" +msgstr "Hoteles Permitidos" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__allowed_reservation_ids +msgid "Allowed Reservations" +msgstr "Reservas Permitidas" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__allowed_room_type_ids +msgid "Allowed Room Types" +msgstr "Tipos de habitaciones autorizadas" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__allowed_room_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__allowed_room_ids +#: model:ir.model.fields,field_description:pms.field_pms_wizard_reservation_lines_split__allowed_room_ids +msgid "Allowed Rooms" +msgstr "Habitaciones Permitidas" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__allowed_service_ids +msgid "Allowed Services" +msgstr "Servicios Permitidos" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_product_pricelist_item__allowed_board_service_product_ids +msgid "Allowed board service products" +msgstr "Productos de servicio a bordo permitidos" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_product_pricelist_item__allowed_board_service_room_type_ids +msgid "Allowed board service room types" +msgstr "Tipos de habitaciones con servicio de comidas permitidos" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__allowed_cancel +msgid "Allowed cancel" +msgstr "Cancelación Permitida" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__allowed_checkin +msgid "Allowed checkin" +msgstr "Acción de checkin" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__allowed_checkout +msgid "Allowed checkout" +msgstr "Método Permitido" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__allowed_pricelist_ids +msgid "Allowed pricelists" +msgstr "Tarifa Permitida" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__allowed_rooms_sources +msgid "Allowed rooms source" +msgstr "Habitaciones de origen permitidas" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__allowed_rooms_target +msgid "Allowed rooms target" +msgstr "Habitaciones destino permitidas" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__allowed_board_services +msgid "Allowed services" +msgstr "Servicios permitidos" + +#. module: pms +#: code:addons/pms/models/pms_board_service.py:0 +#, python-format +msgid "Already exists another Board Service with the same code and properties" +msgstr "" +"Ya existe otro tipo de habitación con el mismo código y las mismas " +"propiedades" + +#. module: pms +#: code:addons/pms/models/pms_room_type_class.py:0 +#, python-format +msgid "" +"Already exists another room type class with the same code and properties" +msgstr "" +"Ya existe otro tipo de habitación con el mismo código y las mismas " +"propiedades" + +#. module: pms +#: code:addons/pms/models/pms_room_type.py:0 +#, python-format +msgid "Already exists another room type with the same code and properties" +msgstr "" +"Ya existe otro tipo de habitación con el mismo código y las mismas " +"propiedades" + +#. module: pms +#: code:addons/pms/models/pms_property.py:0 +#, python-format +msgid "Alreay exist other property with this code: %s (%s)" +msgstr "Ya existe otra propiedad con este código: %s (%s)" + +#. module: pms +#: code:addons/pms/models/pms_property.py:0 +#, python-format +msgid "Alreay exist other property with this ref: %s (%s)" +msgstr "Ya existe otra propiedad con esta referencia: %s (%s)" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__res_partner__invoice_to_agency__always +msgid "Always" +msgstr "Siempre" + +#. module: pms +#: model:ir.ui.menu,name:pms.menu_action_pms_room_amenity_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_form +msgid "Amenities" +msgstr "Características" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_amenity_type__pms_amenity_ids +msgid "Amenities In This Category" +msgstr "Características en esta categoría" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_amenity_type__pms_amenity_ids +msgid "Amenities included in this type" +msgstr "Características en esta categoría" + +#. module: pms +#: model:ir.ui.menu,name:pms.menu_amenity +#: model_terms:ir.ui.view,arch_db:pms.pms_amenity_view_form +msgid "Amenity" +msgstr "Característica" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_amenity__pms_amenity_type_id +msgid "Amenity Category" +msgstr "Categoría de la Característica" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_amenity__name +#: model:ir.model.fields,help:pms.field_pms_amenity__name +msgid "Amenity Name" +msgstr "Nombre de la Característica" + +#. module: pms +#: model:ir.model,name:pms.model_pms_amenity_type +#: model_terms:ir.ui.view,arch_db:pms.pms_amenity_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_room_amenity_type_view_form +msgid "Amenity Type" +msgstr "Tipo de la Característica" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_amenity_type__name +#: model:ir.model.fields,help:pms.field_pms_amenity_type__name +msgid "Amenity Type Name" +msgstr "Nombre del Tipo de la Característica" + +#. module: pms +#: model:ir.ui.menu,name:pms.menu_action_pms_room_amenity_type_view_form +msgid "Amenity Types" +msgstr "Tipos de Características" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_board_service__amount +#: model:ir.model.fields,field_description:pms.field_pms_board_service_line__amount +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type__amount +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type_line__amount +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__amount +msgid "Amount" +msgstr "Importe" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__commission_amount +msgid "Amount corresponding to commission" +msgstr "Importe correspondiente a la comisión" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__invoices_paid +msgid "Amount of invoices paid" +msgstr "Importe no tributable a facturar" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service_line__day_qty +msgid "Amount to be consumed per day" +msgstr "Cantidad por día" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__untaxed_amount_to_invoice +msgid "Amount to invoice" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Amounts" +msgstr "Cantidades" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__analytic_account_id +msgid "Analytic Account" +msgstr "Cuenta Analítica" + +#. module: pms +#: model:ir.model,name:pms.model_account_analytic_distribution +msgid "Analytic Account Distribution" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_account_analytic_line +msgid "Analytic Line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__analytic_tag_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__analytic_tag_ids +#: model:ir.model.fields,field_description:pms.field_pms_service__analytic_tag_ids +msgid "Analytic Tags" +msgstr "Etiquetas Analíticas" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__analytic_line_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__analytic_line_ids +msgid "Analytic lines" +msgstr "Líneas Analíticas" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.product_pricelist_item_view_tree +msgid "Applicable on" +msgstr "Aplicable en" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_on_all_week +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_on_all_week +msgid "Apply Availability Rule for the whole week" +msgstr "Aplicar Regla de Disponibilidad Para Toda la semana" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_on_friday +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_on_friday +msgid "Apply Availability Rule on fridays" +msgstr "Aplicar Regla de Disponibilidad los viernes" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_on_monday +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_on_monday +msgid "Apply Availability Rule on mondays" +msgstr "Aplicar Regla de Disponibilidad los lunes" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_on_saturday +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_on_saturday +msgid "Apply Availability Rule on saturdays" +msgstr "Aplicar Regla de Disponibilidad los sábados" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_on_sunday +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_on_sunday +msgid "Apply Availability Rule on sundays" +msgstr "Aplicar Regla de Disponibilidad los domingos" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_on_thursday +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_on_thursday +msgid "Apply Availability Rule on thursdays" +msgstr "Aplicar Regla de Disponibilidad los jueves" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_on_tuesday +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_on_tuesday +msgid "Apply Availability Rule on tuesdays" +msgstr "Aplicar Regla de Disponibilidad los martes" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_on_wednesday +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_on_wednesday +msgid "Apply Availability Rule on wednesdays" +msgstr "Aplicar Regla de Disponibilidad los miércoles" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_new_checkin +msgid "Apply Checkin Update" +msgstr "Aplicar actualización de facturación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_new_checkout +msgid "Apply Checkout Update" +msgstr "Aplicar actualización de pago" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_partner_id +msgid "Apply Customer" +msgstr "Aplicar Cliente" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_discount +msgid "Apply Discount update" +msgstr "Aplicar actualización de descuento" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__change_from_date +msgid "Apply From" +msgstr "Aplicar desde" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_price +msgid "Apply Price update" +msgstr "Aplicar Actualización de precios" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__apply_pricelist +#: model:ir.model.fields,field_description:pms.field_res_partner__apply_pricelist +#: model:ir.model.fields,field_description:pms.field_res_users__apply_pricelist +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_pricelist_id +msgid "Apply Pricelist" +msgstr "Aplicar Tarifa" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__change_to_date +msgid "Apply To" +msgstr "Aplicar a" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +msgid "Apply and close" +msgstr "Aplicar y cerrar" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +msgid "Apply and continue" +msgstr "Solicitar y continuar" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type__by_default +msgid "Apply by Default" +msgstr "Aplicar por defecto" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_closed +msgid "Apply changes to Closed" +msgstr "Aplicar Cambios a \"Cerrado\"" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_closed_arrival +msgid "Apply changes to Closed Arrival" +msgstr "Aplicar cambios \"Cerrar llegadas\"" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_closed_departure +msgid "Apply changes to Closed Departure" +msgstr "Aplicar cambios \"Cerrar salidas\"" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_max_avail +msgid "Apply changes to Max. Avail." +msgstr "Aplicar Cambios a Max. Avail." + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_max_stay +msgid "Apply changes to Max. Stay" +msgstr "Aplicar Cambios a \"Max estancia\"" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_max_stay_arrival +msgid "Apply changes to Max. Stay Arrival" +msgstr "Aplicar Cambios a \"Max estancia Llegada\"" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_min_stay +msgid "Apply changes to Min. Stay" +msgstr "Aplicar Cambios a \"Min estancia\"" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_min_stay_arrival +msgid "Apply changes to Min. Stay Arrival" +msgstr "Aplicar Cambios a \"Min estancia Llegada\"" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_quota +msgid "Apply changes to Quota" +msgstr "Aplicar Cambios a \"Cupo\"" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_pricelists_on +msgid "Apply pricelists on" +msgstr "Aplicar tarifas en" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service_line__adults +#: model:ir.model.fields,help:pms.field_pms_board_service_room_type_line__adults +msgid "Apply service to adults" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service_line__children +#: model:ir.model.fields,help:pms.field_pms_board_service_room_type_line__children +msgid "Apply service to children" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__state__arrival_delayed +msgid "Arrival Delayed" +msgstr "Llegada restrasada" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__default_arrival_hour +#: model:ir.model.fields,field_description:pms.field_pms_reservation__arrival_hour +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Arrival Hour" +msgstr "Hora de Llegada" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__arrival_hour +msgid "Arrival Hour (HH:MM)" +msgstr "Hora de Llegada (GMT)" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__pms_folio_ids +#: model:ir.model.fields,help:pms.field_res_partner__pms_folio_ids +#: model:ir.model.fields,help:pms.field_res_users__pms_folio_ids +msgid "Associated Folios" +msgstr "Fichas asociadas" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__pms_checkin_partner_ids +#: model:ir.model.fields,help:pms.field_res_partner__pms_checkin_partner_ids +#: model:ir.model.fields,help:pms.field_res_users__pms_checkin_partner_ids +msgid "Associated checkin partners" +msgstr "Checkins asociados" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__pms_reservation_ids +#: model:ir.model.fields,help:pms.field_res_partner__pms_reservation_ids +#: model:ir.model.fields,help:pms.field_res_users__pms_reservation_ids +msgid "Associated reservation" +msgstr "Reserva asociada" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room__ubication_id +msgid "At which ubication the room is located." +msgstr "Ubicación en la que está localizada la habitación." + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__message_attachment_count +#: model:ir.model.fields,field_description:pms.field_pms_folio__message_attachment_count +#: model:ir.model.fields,field_description:pms.field_pms_property__message_attachment_count +#: model:ir.model.fields,field_description:pms.field_pms_reservation__message_attachment_count +#: model:ir.model.fields,field_description:pms.field_pms_room_type__message_attachment_count +msgid "Attachment Count" +msgstr "Nº de adjuntos" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__product_template_attribute_value_ids +msgid "Attribute Values" +msgstr "Valores del Atributo" + +#. module: pms +#: model:ir.model.fields,help:pms.field_res_company__privacy_policy +msgid "Authorization by the user for themanage of their personal data" +msgstr "Autorización del usuario para el tratamiento de sus datos personales" + +#. module: pms +#: model:ir.actions.server,name:pms.autoinvoicing_downpayments_ir_actions_server +#: model:ir.cron,cron_name:pms.autoinvoicing_downpayments +#: model:ir.cron,name:pms.autoinvoicing_downpayments +msgid "Auto Invoicing DownPayments" +msgstr "Anticipos de Facturación Automática" + +#. module: pms +#: model:ir.actions.server,name:pms.autoinvoicing_folios_ir_actions_server +#: model:ir.cron,cron_name:pms.autoinvoicing_folios +#: model:ir.cron,name:pms.autoinvoicing_folios +msgid "Auto Invoicing Folios" +msgstr "Folios de facturación automática" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__is_canceled_auto_mail +msgid "Auto Send Cancellation Mail" +msgstr "Envío automático de correo de cancelación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__is_confirmed_auto_mail +msgid "Auto Send Confirmation Mail" +msgstr "Envío automático de correo de confirmación" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Auto Send Email" +msgstr "Envío automático de correo electrónico" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__is_exit_auto_mail +msgid "Auto Send Exit Mail" +msgstr "Envío Automático de Salida de Correo" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__is_modified_auto_mail +msgid "Auto Send Modification Mail" +msgstr "Envío automático de correo de modificación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__preconfirm +msgid "Auto confirm to Save" +msgstr "Confirmar automáticamente" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__autoinvoice_date +msgid "Autoinvoice Date" +msgstr "Fecha de autofacturación" + +#. module: pms +#: model:ir.model,name:pms.model_pms_automated_mails +msgid "Automatic Mails" +msgstr "Correos automáticos" + +#. module: pms +#: model:ir.actions.server,name:pms.nocheckout_reservations_ir_actions_server +#: model:ir.cron,cron_name:pms.nocheckout_reservations +#: model:ir.cron,name:pms.nocheckout_reservations +msgid "Automatic No Checkout Reservations" +msgstr "No Checkout Reservas automatico" + +#. module: pms +#: model:ir.actions.server,name:pms.noshow_reservations_ir_actions_server +#: model:ir.cron,cron_name:pms.noshow_reservations +#: model:ir.cron,name:pms.noshow_reservations +msgid "Automatic No Show Reservation" +msgstr "Cambio de estado automático a Reservas No Show" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__avail_id +msgid "Avail record" +msgstr "Registro de disponibilidad" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability__avail_rule_ids +msgid "Avail record rules" +msgstr "Reglas de disponibilidad" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__avail_id +msgid "Availability Day" +msgstr "Disponibilidad Día" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__availability_plan_id +#: model:ir.model.fields,field_description:pms.field_product_pricelist__availability_plan_id +#: model:ir.model.fields.selection,name:pms.selection__pms_massive_changes_wizard__massive_changes_on__availability_plan +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +msgid "Availability Plan" +msgstr "Plan de Disponibilidad" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan__name +msgid "Availability Plan Name" +msgstr "Nombre del Plan de Disponibilidad" + +#. module: pms +#: model:ir.actions.act_window,name:pms.availability_plan_rule_view_tree_action +msgid "Availability Plan Rules" +msgstr "Reglas de disponibilidad" + +#. module: pms +#: model:ir.model.fields,help:pms.field_product_pricelist__availability_plan_id +msgid "Availability Plan for which the pricelist is included" +msgstr "Plan de disponibilidad para el que se incluye la lista de precios " + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__availability_plan_ids +msgid "Availability Plan to apply massive changes" +msgstr "Plan de Disponibilidad para aplicar cambios masivos" + +#. module: pms +#: model:ir.ui.menu,name:pms.reservation_availability_plan_rules_menu +#: model_terms:ir.ui.view,arch_db:pms.availability_plan_rule_view_form +msgid "Availability Plans" +msgstr "Planes de Disponibilidad" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__availability_results +#: model:ir.model.fields,help:pms.field_pms_booking_engine__availability_results +msgid "Availability Results" +msgstr "Resultados de la Disponibilidad" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan__rule_ids +#: model_terms:ir.ui.view,arch_db:pms.availability_view_form +msgid "Availability Rules" +msgstr "Reglas de Disponibilidad" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_product_pricelist__pms_sale_channel_ids +msgid "Available Channels" +msgstr "Canales de Venta" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_product_pricelist__is_pms_available +msgid "Available in PMS" +msgstr "Disponible en el Punto De Venta" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__available_in_pos +msgid "Available in POS" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__num_rooms_available +msgid "Available rooms" +msgstr "Habitaciones Disponibles" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Avatar" +msgstr "Avatar" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__avail_readonly +msgid "Avialability Readonly" +msgstr "Disponibilidad solo lectura" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_account_journal__avoid_autoinvoice_downpayment +#: model:ir.model.fields,help:pms.field_account_journal__avoid_autoinvoice_downpayment +msgid "Avoid autoinvoice downpayment" +msgstr "Evitar el pago a cuenta de la autofactura" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__avoid_mails +msgid "Avoid comunication mails" +msgstr "Evitar los correos de comunicación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__avoid_simplified_max_amount_downpayment +msgid "Avoid simplified invoice max amount downpayment" +msgstr "Evitar el anticipo máximo de la factura simplificada" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__bank_account_count +msgid "Bank" +msgstr "Banco" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__payment_ids +msgid "Bank Payments" +msgstr "Pagos bancarios" + +#. module: pms +#: model:ir.model,name:pms.model_account_bank_statement +msgid "Bank Statement" +msgstr "Extracto bancario" + +#. module: pms +#: model:ir.model,name:pms.model_account_bank_statement_line +msgid "Bank Statement Line" +msgstr "Línea del Extracto Bancario" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__bank_ids +msgid "Banks" +msgstr "Bancos" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__barcode +#: model:ir.model.fields,field_description:pms.field_pms_room_type__barcode +msgid "Barcode" +msgstr "Código de Barras" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__product_template__consumed_on__before +msgid "Before night" +msgstr "Antes de la noche" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__bill_rooms +#: model:ir.model.fields,help:pms.field_folio_advance_payment_inv__bill_rooms +msgid "Bill Rooms" +msgstr "Facturar Habitaciones" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__bill_services +#: model:ir.model.fields,help:pms.field_folio_advance_payment_inv__bill_services +msgid "Bill Services" +msgstr "Facturar Servicios" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__partner_invoice_ids +msgid "Billing addresses" +msgstr "Dirección de facturación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__partner_invoice_id +msgid "Billing contact" +msgstr "Contacto de facturación" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Birth Date *" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__birthdate_date +#: model:ir.model.fields,field_description:pms.field_pms_property__birthdate_date +#: model:ir.model.fields,field_description:pms.field_res_partner__birthdate_date +#: model:ir.model.fields,field_description:pms.field_res_users__birthdate_date +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "Birthdate" +msgstr "Fecha de nacimiento" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__is_blacklisted +msgid "Blacklist" +msgstr "Lista Negra" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__blocked +msgid "Blocked" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#: code:addons/pms/models/pms_reservation_line.py:0 +#, python-format +msgid "Blocked reservations can't be modified" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_pms_board_service_room_type_view +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__is_board_service +#: model:ir.model.fields,field_description:pms.field_pms_board_service_line__pms_board_service_id +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type__pms_board_service_id +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__board_service_room_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__board_service_room_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__board_service_room_id +#: model:ir.model.fields,field_description:pms.field_product_pricelist_item__board_service_room_type_id +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_report_view_tree +msgid "Board Service" +msgstr "Régimen" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_board_service__default_code +msgid "Board Service Code" +msgstr "Código del régimen" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_service__board_service_line_id +#: model_terms:ir.ui.view,arch_db:pms.pms_board_service_form +#: model_terms:ir.ui.view,arch_db:pms.pms_board_service_room_type_form +msgid "Board Service Line" +msgstr "Servicios del Régimen" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__board_service_line_id +msgid "Board Service Line in which this service is included" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_board_service__board_service_line_ids +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type__board_service_line_ids +msgid "Board Service Lines" +msgstr "Servicios del Régimen" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_board_service__name +#: model:ir.model.fields,help:pms.field_pms_board_service__name +msgid "Board Service Name" +msgstr "Servicios del Régimen" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__board_price +#: model:ir.model.fields,field_description:pms.field_product_product__board_price +msgid "Board Service Price" +msgstr "Precio del Régimen" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type_line__pms_board_service_room_type_id +msgid "Board Service Room" +msgstr "Habitación del Régimen" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service_room_type_line__pms_board_service_room_type_id +msgid "Board Service Room Type in which this line is included" +msgstr "Servicio de Pensión Tipo de Habitación en el que se incluye esta línea" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service_room_type__pms_board_service_id +msgid "Board Service corresponding to this Board Service Room Type" +msgstr "" +"Servicio de Pensión correspondiente a este Tipo de Habitación de Servicio de " +"Pensión" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service_line__pms_board_service_id +msgid "Board Service in which this line is included" +msgstr "Servicio de la Junta en el que se incluye esta línea" + +#. module: pms +#: model:ir.model,name:pms.model_pms_board_service_room_type +msgid "Board Service included in Room" +msgstr "Régimen incluido en la Habitación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__board_service_room_type_ids +msgid "Board Service included in room type" +msgstr "Régimen incluido en el Tipo de Habitación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio_availability_wizard__board_service_room_id +#: model:ir.model.fields,help:pms.field_pms_reservation__board_service_room_id +#: model:ir.model.fields,help:pms.field_pms_reservation_duplicate__board_service_room_id +msgid "Board Service included in the room" +msgstr "Régimen incluido en la Habitación" + +#. module: pms +#: model:ir.actions.act_window,name:pms.open_pms_board_service_form_tree +#: model:ir.model,name:pms.model_pms_board_service +#: model:ir.model.fields,field_description:pms.field_pms_room_type__board_service_room_type_ids +#: model:ir.model.fields.selection,name:pms.selection__pms_massive_changes_wizard__apply_pricelists_on__board_services +#: model:ir.ui.menu,name:pms.menu_open_pms_board_service_form_tree +#: model_terms:ir.ui.view,arch_db:pms.pms_room_type_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "Board Services" +msgstr "Regímenes" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_board_service__pms_board_service_room_type_ids +msgid "Board Services Room Type" +msgstr "Pms Board Service Room Type" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service__pms_board_service_room_type_ids +msgid "" +"Board Services Room Type corresponding to this Board Service,One board " +"service for several room types" +msgstr "" +"Servicios de pensión Tipo de habitación correspondiente a este servicio de " +"pensión,Un servicio de pensión para varios tipos de habitación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__board_service +msgid "Board service" +msgstr "Regimen" + +#. module: pms +#. openerp-web +#: code:addons/pms/static/src/xml/reservation_group_button_views.xml:0 +#: model:ir.model.fields,field_description:pms.field_pms_num_rooms_selection__booking_engine_id +#: model:ir.ui.menu,name:pms.menu_pms_booking_engine +#, python-format +msgid "Booking Engine" +msgstr "Motor de Reservas" + +#. module: pms +#: model:ir.model,name:pms.model_pms_booking_engine +msgid "Booking engine" +msgstr "Motor de Reservas" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__message_bounce +msgid "Bounce" +msgstr "Devuelto" + +#. module: pms +#: model:pms.board.service,name:pms.pms_board_service_breakfast +msgid "BreakFast" +msgstr "Desayuno" + +#. module: pms +#: model:product.product,name:pms.pms_service_breakfast_buffet +#: model:product.template,name:pms.pms_service_breakfast_buffet_product_template +msgid "Breakfast Buffet" +msgstr "Desayuno Buffet" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "By Day" +msgstr "Por Día" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "By Month" +msgstr "Por Mes" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "By Week" +msgstr "Por Semana" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "CIF:" +msgstr "NIF:" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__can_image_1024_be_zoomed +msgid "Can Image 1024 be zoomed" +msgstr "Se puede ampliar la imagen 1024" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__can_image_variant_1024_be_zoomed +msgid "Can Variant Image 1024 be zoomed" +msgstr "Se puede ampliar la imagen variante 1024" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__purchase_ok +msgid "Can be Purchased" +msgstr "Puede ser comprado" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__sale_ok +msgid "Can be Sold" +msgstr "Puede ser vendido" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__can_create_folio +msgid "Can create folio" +msgstr "Puede Crear un Folio" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.advanced_filters_wizard +#: model_terms:ir.ui.view,arch_db:pms.booking_duplicate +#: model_terms:ir.ui.view,arch_db:pms.booking_engine +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.reservation_wizard +#: model_terms:ir.ui.view,arch_db:pms.several_partners_wizard +#: model_terms:ir.ui.view,arch_db:pms.view_folio_advance_payment_inv +#: model_terms:ir.ui.view,arch_db:pms.wizard_folio_changes_view_form +#: model_terms:ir.ui.view,arch_db:pms.wizard_payment_folio_view_form +msgid "Cancel" +msgstr "Cancelar" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__cancel_datetime +msgid "Cancel Date" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Cancel Folio" +msgstr "Cancelar Folio" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Cancel Penalty" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Cancel Reservation" +msgstr "Cancelar Reserva" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_res_company__cancel_penalty_product_id +msgid "Cancel penalty product" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_service_line__cancel_discount +msgid "Cancelation Discount" +msgstr "Descuento por Cancelación (%)" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__cancel_discount +msgid "Cancelation Discount (%)" +msgstr "Descuento por Cancelación (%)" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_product_pricelist__cancelation_rule_id +msgid "Cancelation Policy" +msgstr "Política de Cancelación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_product_pricelist__cancelation_rule_id +msgid "Cancelation Policy included in the room" +msgstr "Política de Cancelación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__name +msgid "Cancelation Rule" +msgstr "Regla de Cancelación" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_pms_cancelation_rule +#: model:ir.model,name:pms.model_pms_cancelation_rule +#: model:ir.ui.menu,name:pms.menu_pms_cancelation_rule +#: model_terms:ir.ui.view,arch_db:pms.pms_cancelation_rule_form +msgid "Cancelation Rules" +msgstr "Reglas de Cancelación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__property_canceled_template +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Cancellation Email" +msgstr "Correo Electrónico de Cancelación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__property_canceled_template +msgid "Cancellation email template" +msgstr "Plantilla de correo electrónico de cancelación" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_checkin_partner__state__cancel +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__state__cancel +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__state__cancel +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_kanban_view +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Cancelled" +msgstr "Cancelado" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.report_invoice_document +msgid "Cancelled Invoice" +msgstr "Factura cancelada" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Cancelled Rooms" +msgstr "Reservas canceladas" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.report_invoice_document +msgid "Cancelled Simplified Invoice" +msgstr "Factura Simplificada Cancelada" + +#. module: pms +#: code:addons/pms/models/ir_config_parameter.py:0 +#, python-format +msgid "Cannot delete this parameter" +msgstr "No puede borrar este parámetro" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room__capacity +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_kanban +msgid "Capacity" +msgstr "Capacidad" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.company_view_form +msgid "Cardex Settings" +msgstr "Configuración de cardex" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__statement_line_ids +msgid "Cash Payments" +msgstr "Pagos de Efectivo" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_res_partner_id_number__category_id +msgid "Category" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__route_from_categ_ids +msgid "Category Routes" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__pos_categ_id +msgid "Category used in the Point of Sale." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__cancelled_reason +msgid "Cause of cancelled" +msgstr "Causa de la Cancelación" + +#. module: pms +#: model:ir.model,name:pms.model_room_closure_reason +#: model:ir.model.fields,field_description:pms.field_pms_folio__out_service_description +#: model:ir.model.fields,field_description:pms.field_pms_reservation__out_service_description +msgid "Cause of out of service" +msgstr "Causa de estar Fuera de Servicio" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.wizard_folio_changes_view_form +msgid "Change" +msgstr "Cambiar" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_day_qty +msgid "Change cuantity service per day" +msgstr "Cambiar la cantidad de servicio por día" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__channel_ids +msgid "Channels" +msgstr "Canales de venta" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__check_adults +msgid "Check Adults" +msgstr "Comprobar adultos" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__checkin +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__checkin +msgid "Check In" +msgstr "Entrada" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__checkout +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__checkout +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Check Out" +msgstr "Salida" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__is_origin_channel_check_visible +msgid "Check force update origin visible" +msgstr "Comprobar el origen de actualización forzada visible" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__is_company +msgid "Check if the contact is a company, otherwise it is a person" +msgstr "" +"Comprobar que el contacto es una compañía, de otra manera es una persona" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__to_weight +msgid "" +"Check if the product should be weighted using the hardware scale integration." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_duplicate__occupied_room +msgid "Check if the room is occupied" +msgstr "Compruebe si la habitación está ocupada" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__available_in_pos +msgid "Check if you want this product to appear in the Point of Sale." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_res_company__check_min_partner_data_invoice +msgid "Check minimum partner data for invoices" +msgstr "Comprobar los datos mínimos de los interlocutores para las facturas" + +#. module: pms +#: model:ir.model.fields,help:pms.field_res_company__check_min_partner_data_invoice +msgid "" +"Check minimum partner data for invoices:\n" +" - VAT, name, street, city, country" +msgstr "" +"Compruebe los datos mínimos del interlocutor para las facturas:\n" +" - IVA, nombre, calle, ciudad, país" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__employee +msgid "Check this box if this contact is an Employee." +msgstr "Marque esta casilla si este contacto es un Empleado." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__aeat_anonymous_cash_customer +msgid "Check this for anonymous cash customer. AEAT communication" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Check-in hours" +msgstr "Horas del Check-in" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__checkin +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation +msgid "Checkin" +msgstr "Entrada" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__pending_checkin_data +#: model:ir.model.fields,field_description:pms.field_pms_reservation__pending_checkin_data +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Checkin Data" +msgstr "Datos del Checkin" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Checkin Date" +msgstr "Fecha del Checkin" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +msgid "Checkin Detail" +msgstr "Detalles del Checkin" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_several_partners_wizard__checkin_partner_id +msgid "Checkin Partner" +msgstr "Registro de Entrada de Socio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__email +msgid "Checkin Partner Email" +msgstr "Email del Checkin" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__identifier +msgid "Checkin Partner Id" +msgstr "Cliente del Checkin" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__image_128 +msgid "Checkin Partner Image, it corresponds with Partner Image associated" +msgstr "" +"Imagen de Checkin Partner, se corresponde con la imagen de Partner asociada " + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__mobile +msgid "Checkin Partner Mobile" +msgstr "Móvil del Checkin" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__phone +msgid "Checkin Partner Phone" +msgstr "Teléfono de Facturación del Socio" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__checkin_partner_ids +#: model:ir.model.fields,field_description:pms.field_pms_property__pms_checkin_partner_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__checkin_partner_ids +#: model:ir.model.fields,field_description:pms.field_res_partner__pms_checkin_partner_ids +#: model:ir.model.fields,field_description:pms.field_res_users__pms_checkin_partner_ids +msgid "Checkin Partners" +msgstr "Checkins" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__checkin_partner_pending_count +msgid "Checkin Pending Num" +msgstr "Número Pendiente de checkins" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__checkin_sequence_id +msgid "Checkin Sequence" +msgstr "Secuencia del Checkin" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +msgid "Checkin by" +msgstr "Checkin por" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +msgid "Checkin by Day" +msgstr "Checkin por Día" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +msgid "Checkin by Month" +msgstr "Checkin por Mes" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +msgid "Checkin by Week" +msgstr "Checkin por Semana" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__checkin_partner_count +msgid "Checkin counter" +msgstr "Nº de Checkin" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__checkin +msgid "Checkin date" +msgstr "Fecha del Checkin" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_split_join_swap_wizard__checkin +msgid "Checkin in reservation" +msgstr "Checkin en reserva" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__arrival +msgid "Checkin partner arrival date and time" +msgstr "Fecha y hora de llegada del Checkin Partner" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__departure +msgid "Checkin partner departure date and time" +msgstr "Fecha y hora de salida del Checkin Partner" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__name +msgid "Checkin partner name" +msgstr "Cliente del Checkin" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__sii_simplified_invoice +msgid "" +"Checking this mark, invoices done to this partner will be sent to SII as " +"simplified invoices." +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#: model:ir.actions.act_window,name:pms.action_checkin_partner +#: model:ir.ui.menu,name:pms.menu_pms_checkin_partner +#, python-format +msgid "Checkins" +msgstr "Registros de entrada" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +msgid "Checkins Today" +msgstr "Checkin Hoy" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +msgid "Checkins Tomorrow" +msgstr "Checkins mañana" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +msgid "Checkins to 7 days" +msgstr "Checkins para 7 días" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__checkout +#: model:ir.model.fields.selection,name:pms.selection__pms_property__default_invoicing_policy__checkout +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation +msgid "Checkout" +msgstr "Checkout" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +msgid "Checkout by" +msgstr "Checkout por" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +msgid "Checkout by Day" +msgstr "Checkout por Día" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +msgid "Checkout by Month" +msgstr "Checkout por Mes" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +msgid "Checkout by Week" +msgstr "Checkout por Seamana" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__checkout +msgid "Checkout date" +msgstr "Fecha del Checkin" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__res_company__pms_invoice_downpayment_policy__checkout_past_month +msgid "Checkout past month" +msgstr "Comprobación del mes pasado" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability__child_avail_ids +msgid "Child Avails" +msgstr "Niño Disponible" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__child_ids +msgid "Child Properties" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room__child_ids +msgid "Child Rooms" +msgstr "Habitaciones Infantiles" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability__child_avail_ids +msgid "Child availabilities for this availability" +msgstr "Disponibilidades infantiles para esta disponibilidad" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room__child_ids +msgid "Child rooms of the room" +msgstr "Habitaciones infantiles de la sala" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_board_service_line__children +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type_line__children +#: model:ir.model.fields,field_description:pms.field_pms_reservation__children +msgid "Children" +msgstr "Niños" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__children_occupying +msgid "Children occupying" +msgstr "Niños que ocupan" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.several_partners_wizard +msgid "Choose a customer if you want to add it to the reservation" +msgstr "Seleccione un cliente si desea añadirlo a la reserva" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__residence_city +#: model:ir.model.fields,field_description:pms.field_pms_property__city +#: model:ir.model.fields,field_description:pms.field_res_partner__city +#: model:ir.model.fields,field_description:pms.field_res_users__city +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +#: model_terms:ir.ui.view,arch_db:pms.res_partner_view_form +msgid "City" +msgstr "Ciudad" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__residence_city +#: model:ir.model.fields,help:pms.field_pms_property__residence_city +#: model:ir.model.fields,help:pms.field_res_partner__residence_city +#: model:ir.model.fields,help:pms.field_res_users__residence_city +msgid "City of the guest's residence" +msgstr "Ciudad de residencia del huésped" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class__name +msgid "Class Name" +msgstr "Nombre de clase" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__class_id +msgid "Class to which the room type belongs" +msgstr "Clase a la que pertenece el tipo de habitación" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_folio_payment +msgid "Close" +msgstr "Cerrar" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__closed +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__closed +msgid "Closed" +msgstr "Cerrado" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__closed_arrival +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__closed_arrival +msgid "Closed Arrival" +msgstr "***Llegada cerrada" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__closed_departure +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__closed_departure +msgid "Closed Departure" +msgstr "Salida cerrada***" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__closure_reason_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__closure_reason_id +msgid "Closure Reason" +msgstr "Razón de Cierre" + +#. module: pms +#: model:ir.ui.menu,name:pms.menu_pms_room_closure_reason_form_tree +msgid "Closure Reasons" +msgstr "Razones de Cierre" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Closure reason" +msgstr "Razón de Cierre" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__default_code +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class__default_code +msgid "Code" +msgstr "Código" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__color +#: model:ir.model.fields,field_description:pms.field_pms_room_type__color +msgid "Color Index" +msgstr "Índice de Color" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__combination_indices +msgid "Combination Indices" +msgstr "Índices combinados" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Comments about the customer" +msgstr "Comentarios sobre el cliente" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__commercial_partner_id +msgid "Commercial Entity" +msgstr "Entidad Comercial" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__commission +#: model:ir.model.fields,field_description:pms.field_pms_property__default_commission +#: model:ir.model.fields,field_description:pms.field_res_partner__default_commission +#: model:ir.model.fields,field_description:pms.field_res_users__default_commission +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_tree +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_tree +msgid "Commission" +msgstr "Comisión" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Commission Amount" +msgstr "Importe de la Comisión" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__commission_amount +msgid "Commission amount" +msgstr "Importe de la Comisión" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__commission_percent +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Commission percent (%)" +msgstr "Porcentaje de la Comisión (%)" + +#. module: pms +#: model:ir.model,name:pms.model_res_company +msgid "Companies" +msgstr "Compañías" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__ref_company_ids +msgid "Companies that refers to partner" +msgstr "Compañías que se refieren a un cliente" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__company_id +#: model:ir.model.fields,field_description:pms.field_pms_folio__company_id +#: model:ir.model.fields,field_description:pms.field_pms_property__company_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__company_id +#: model:ir.model.fields,field_description:pms.field_pms_room_type__company_id +#: model:ir.model.fields,field_description:pms.field_pms_service__company_id +#: model:ir.model.fields,field_description:pms.field_product_pricelist__company_id +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +msgid "Company" +msgstr "Compañía" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__company_name +msgid "Company Name" +msgstr "Nombre de la Compañía" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__commercial_company_name +msgid "Company Name Entity" +msgstr "Nombre de la Entidad de la Compañía" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__company_type +msgid "Company Type" +msgstr "Tipo de Compañía" + +#. module: pms +#: model:ir.model.fields,help:pms.field_product_pricelist__company_id +msgid "Company to which the pricelist belongs" +msgstr "Empresa a la que pertenece la tarifa" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__company_id +msgid "Company to which the reservation belongs" +msgstr "Empresa a la que pertenece la reserva" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__company_id +msgid "Company to which the service belongs" +msgstr "Empresa a la que pertenece el servicio" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__contact_address +msgid "Complete Address" +msgstr "Dirección Completa" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__ratio_checkin_data +msgid "Complete cardex" +msgstr "Cardex completo" + +#. module: pms +#: model:pms.room.type.class,name:pms.pms_room_type_class_conference_room +msgid "Conference" +msgstr "Conferencia" + +#. module: pms +#: model:pms.room.type,name:pms.pms_room_type_conference_room +#: model:product.product,name:pms.pms_room_type_conference_room_product_product +msgid "Conference Room" +msgstr "Habitación para Conferencias" + +#. module: pms +#: model:ir.ui.menu,name:pms.pms_configuration_menu +msgid "Configuration" +msgstr "Configuración" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Confirm" +msgstr "Confirmar" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Confirm Assigned Room" +msgstr "Confirmar habitación asignada" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Confirm Sale" +msgstr "Confirmar Venta" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__confirmation_date +msgid "Confirmation Date" +msgstr "Fecha de Confirmación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__property_confirmed_template +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Confirmation Email" +msgstr "Correo Electrónico de Confirmación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__property_confirmed_template +msgid "Confirmation email template" +msgstr "Plantilla de correo electrónico de confirmación" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__state__confirm +msgid "Confirmed" +msgstr "Confirmado" + +#. module: pms +#: model:pms.amenity.type,name:pms.pms_amenity_type_connectivity +msgid "Connectivity" +msgstr "Conectividad" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__consumed_on +#: model:ir.model.fields,field_description:pms.field_product_product__consumed_on +#: model:ir.model.fields,field_description:pms.field_product_template__consumed_on +msgid "Consumed" +msgstr "Consumido" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_massive_changes_wizard__date_types__consumption_dates +msgid "Consumption Dates" +msgstr "Fechas de Consumo" + +#. module: pms +#: model:ir.model,name:pms.model_res_partner +#: model_terms:ir.ui.view,arch_db:pms.folio_portal_template +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation_detail +msgid "Contact" +msgstr "Contacto" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Contact Invoiced" +msgstr "Contacto Facturado" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_kanban_view +msgid "Contact image" +msgstr "Imagen de contacto" + +#. module: pms +#: model:ir.ui.menu,name:pms.pms_contacts_menu +msgid "Contacts" +msgstr "Contactos" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__standard_price +msgid "Cost" +msgstr "Precio" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__cost_currency_id +msgid "Cost Currency" +msgstr "Moneda del Precio" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__cost_method +msgid "Costing Method" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__payment_token_count +msgid "Count Payment Token" +msgstr "Contar Señal de Pago" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__count_alternative_free_rooms +msgid "Count alternative free rooms" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__message_bounce +msgid "Counter of the number of bounced emails for this contact" +msgstr "Nº emails devueltos por este contacto" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_res_partner_id_category__country_ids +msgid "Countries" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_res_country +#: model:ir.model.fields,field_description:pms.field_pms_property__country_id +#: model:ir.model.fields,field_description:pms.field_res_partner__country_id +#: model:ir.model.fields,field_description:pms.field_res_partner_id_number__country_id +#: model:ir.model.fields,field_description:pms.field_res_users__country_id +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +#: model_terms:ir.ui.view,arch_db:pms.res_partner_view_form +msgid "Country" +msgstr "País" + +#. module: pms +#: code:addons/pms/models/res_partner_id_number.py:0 +#, python-format +msgid "Country is not allowed for this document type" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__residence_country_id +#: model:ir.model.fields,field_description:pms.field_pms_property__residence_country_id +#: model:ir.model.fields,field_description:pms.field_res_partner__residence_country_id +#: model:ir.model.fields,field_description:pms.field_res_users__residence_country_id +msgid "Country of residence" +msgstr "País de residencia" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__document_country_id +#: model:ir.model.fields,help:pms.field_res_partner_id_number__country_id +msgid "Country of the document" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__residence_country_id +msgid "Country of the guest's residence" +msgstr "País de residencia del huésped" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +msgid "Create Date" +msgstr "Fecha de Creación" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.booking_engine +msgid "Create Folio" +msgstr "Crear Ficha" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__advance_payment_method +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.view_folio_advance_payment_inv +msgid "Create Invoice" +msgstr "Crear Factura" + +#. module: pms +#: model_terms:ir.actions.act_window,help:pms.action_pms_move_out_invoice_type +msgid "Create a customer invoice" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.booking_duplicate +msgid "Create and Close" +msgstr "Crear y cerrar" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.booking_duplicate +msgid "Create and Continue" +msgstr "Crear y Continuar" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.view_folio_advance_payment_inv +msgid "Create and View Invoice" +msgstr "Crear y Ver Factura" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +msgid "Create by" +msgstr "Creado por" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +msgid "Create by Day" +msgstr "Crear por Día" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +msgid "Create by Month" +msgstr "Crear por Mes" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +msgid "Create by Week" +msgstr "Crear por Semana" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_view_folio_advance_payment_inv +msgid "Create invoices" +msgstr "Crear Facturas" + +#. module: pms +#: model_terms:ir.actions.act_window,help:pms.action_pms_move_out_invoice_type +msgid "" +"Create invoices, register payments and keep track of the discussions with " +"your customers." +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Created By" +msgstr "Creado por" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__create_uid +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__create_uid +#: model:ir.model.fields,field_description:pms.field_ir_pms_property__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_advanced_filters_wizard__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_amenity__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_amenity_type__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_automated_mails__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_availability__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_board_service__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_board_service_line__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type_line__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_folio__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_num_rooms_selection__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_property__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_reservation__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_room__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_room_type__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_sale_channel__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_service__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_service_line__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_several_partners_wizard__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_team_member__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_ubication__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_wizard_reservation_lines_split__create_uid +#: model:ir.model.fields,field_description:pms.field_room_closure_reason__create_uid +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__create_uid +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__create_date +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__create_date +#: model:ir.model.fields,field_description:pms.field_ir_pms_property__create_date +#: model:ir.model.fields,field_description:pms.field_pms_advanced_filters_wizard__create_date +#: model:ir.model.fields,field_description:pms.field_pms_amenity__create_date +#: model:ir.model.fields,field_description:pms.field_pms_amenity_type__create_date +#: model:ir.model.fields,field_description:pms.field_pms_automated_mails__create_date +#: model:ir.model.fields,field_description:pms.field_pms_availability__create_date +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan__create_date +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__create_date +#: model:ir.model.fields,field_description:pms.field_pms_board_service__create_date +#: model:ir.model.fields,field_description:pms.field_pms_board_service_line__create_date +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type_line__create_date +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__create_date +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__create_date +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__create_date +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__create_date +#: model:ir.model.fields,field_description:pms.field_pms_folio__create_date +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__create_date +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__create_date +#: model:ir.model.fields,field_description:pms.field_pms_num_rooms_selection__create_date +#: model:ir.model.fields,field_description:pms.field_pms_property__create_date +#: model:ir.model.fields,field_description:pms.field_pms_reservation__create_date +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__create_date +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__create_date +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__create_date +#: model:ir.model.fields,field_description:pms.field_pms_room__create_date +#: model:ir.model.fields,field_description:pms.field_pms_room_type__create_date +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class__create_date +#: model:ir.model.fields,field_description:pms.field_pms_sale_channel__create_date +#: model:ir.model.fields,field_description:pms.field_pms_service__create_date +#: model:ir.model.fields,field_description:pms.field_pms_service_line__create_date +#: model:ir.model.fields,field_description:pms.field_pms_several_partners_wizard__create_date +#: model:ir.model.fields,field_description:pms.field_pms_team_member__create_date +#: model:ir.model.fields,field_description:pms.field_pms_ubication__create_date +#: model:ir.model.fields,field_description:pms.field_pms_wizard_reservation_lines_split__create_date +#: model:ir.model.fields,field_description:pms.field_room_closure_reason__create_date +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__create_date +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__create_date +msgid "Created on" +msgstr "Creado en" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Creation Date" +msgstr "Fecha de Creación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__credit_card_details +#: model:ir.model.fields,field_description:pms.field_pms_reservation__credit_card_details +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Credit Card Details" +msgstr "Detalles de la Tarjeta de Crédito" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__credit_limit +msgid "Credit Limit" +msgstr "Límite de crédito" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.report_invoice_document +msgid "Credit Note" +msgstr "Nota de Crédito" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__currency_id +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__currency_id +#: model:ir.model.fields,field_description:pms.field_pms_folio__currency_id +#: model:ir.model.fields,field_description:pms.field_pms_property__currency_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__currency_id +#: model:ir.model.fields,field_description:pms.field_pms_room_type__currency_id +#: model:ir.model.fields,field_description:pms.field_pms_service__currency_id +#: model:ir.model.fields,field_description:pms.field_pms_service_line__currency_id +msgid "Currency" +msgstr "Moneda" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_advance_payment_inv__currency_id +msgid "Currency used in invoices" +msgstr "Moneda usada en facturas" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "Current Booking" +msgstr "Reserva Actual" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.res_users_view_form +msgid "Current Property" +msgstr "Hotel Actual" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__partner_id +msgid "Current property" +msgstr "Hotel Actual" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__qty_available +msgid "" +"Current quantity of products.\n" +"In a context with a single Stock Location, this includes goods stored at " +"this Location, or any of its children.\n" +"In a context with a single Warehouse, this includes goods stored in the " +"Stock Location of this Warehouse, or any of its children.\n" +"stored in the Stock Location of the Warehouse of this Shop, or any of its " +"children.\n" +"Otherwise, this includes goods stored in any Stock Location with 'internal' " +"type." +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation_detail +msgid "Current state of this reservation" +msgstr "Actual estado de la reserva" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__product_custom_attribute_value_ids +msgid "Custom Values" +msgstr "Valores Personalizados" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__partner_id +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__new_partner_id +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +msgid "Customer" +msgstr "Cliente" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__email +#: model:ir.model.fields,help:pms.field_pms_reservation__email +msgid "Customer E-mail" +msgstr "E-mail" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__sale_delay +msgid "Customer Lead Time" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__property_stock_customer +msgid "Customer Location" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__mobile +#: model:ir.model.fields,help:pms.field_pms_reservation__mobile +msgid "Customer Mobile" +msgstr "Móvil" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__partner_name +#: model:ir.model.fields,field_description:pms.field_pms_reservation__partner_name +msgid "Customer Name" +msgstr "Nombre" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__property_payment_term_id +msgid "Customer Payment Terms" +msgstr "Términos de Pago del Cliente" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__access_url +#: model:ir.model.fields,help:pms.field_pms_folio__access_url +#: model:ir.model.fields,help:pms.field_pms_reservation__access_url +msgid "Customer Portal URL" +msgstr "URL del Portal del Cliente" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__customer_rank +msgid "Customer Rank" +msgstr "Rango del Cliente" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__partner_ref +msgid "Customer Ref" +msgstr "Ref. del Cliente" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__deposit_taxes_id +#: model:ir.model.fields,field_description:pms.field_pms_room_type__taxes_id +#: model_terms:ir.ui.view,arch_db:pms.pms_amenity_view_form +msgid "Customer Taxes" +msgstr "Impuestos del Cliente" + +#. module: pms +#: model:ir.actions.act_window,name:pms.pms_customer_action +#: model:ir.model.fields,field_description:pms.field_pms_several_partners_wizard__possible_existing_customer_ids +#: model:ir.ui.menu,name:pms.pms_customer_meu +msgid "Customers" +msgstr "Clientes" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__product_pricelist__pricelist_type__daily +msgid "Daily Plan" +msgstr "Plan Diario" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__daily_limit +#: model:ir.model.fields,field_description:pms.field_product_product__daily_limit +#: model:ir.model.fields,field_description:pms.field_product_template__daily_limit +msgid "Daily limit" +msgstr "Límite Diario" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__pending_checkin_data +msgid "Data missing at checkin" +msgstr "Faltan datos personales en el checkin" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__date_order +#: model:ir.model.fields,field_description:pms.field_pms_availability__date +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__date +#: model:ir.model.fields,field_description:pms.field_pms_property__date +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__date +#: model:ir.model.fields,field_description:pms.field_pms_service_line__date +#: model:ir.model.fields,field_description:pms.field_pms_wizard_reservation_lines_split__date +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__date +msgid "Date" +msgstr "Fecha" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__date_order +msgid "Date Order" +msgstr "Fecha del Pedido" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio_availability_wizard__checkout +#: model:ir.model.fields,help:pms.field_pms_reservation_duplicate__checkout +msgid "Date Reservation ends" +msgstr "Fecha de finalización de la reserva" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio_availability_wizard__checkin +#: model:ir.model.fields,help:pms.field_pms_reservation_duplicate__checkin +msgid "Date Reservation starts " +msgstr "Fecha de inicio de la reserva " + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__sign_on +msgid "Date and time of the signature" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability__date +msgid "Date for which availability applies" +msgstr "Fecha para la que se aplica disponibilidad" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan_rule__date +msgid "Date for which availability rule applies" +msgstr "Fecha para la que se aplica la regla de disponibilidad" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__start_date +msgid "Date from first copy Checkin (reference min checkin folio reservation)" +msgstr "" +"Fecha de la primera copia Registro de entrada (referencia min. registro " +"reserva folio)" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__document_expedition_date +msgid "Date on which document_type was issued" +msgstr "Fecha en que se emitió el documento" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__date_order +msgid "Date on which folio is sold" +msgstr "Fecha en la cual se confirmó el folio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__confirmation_date +msgid "Date on which the folio is confirmed." +msgstr "Fecha en la cual se confirma el folio." + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__date_types +msgid "Date types" +msgstr "Tipo de fecha" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__cancel_datetime +msgid "Date when the reservation was cancelled" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__wizard_folio_changes__modification_type__dates +msgid "Dates" +msgstr "Fechas" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__dates_incongruence +msgid "Dates incrongruence" +msgstr "Incrongruencia de fechas" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__days_intime +msgid "Days Late" +msgstr "Nº de días pre-checkin" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_res_partner__margin_days_autoinvoice +#: model:ir.model.fields,field_description:pms.field_res_users__margin_days_autoinvoice +msgid "Days from Checkout" +msgstr "Días desde la salida" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__margin_days_autoinvoice +#: model:ir.model.fields,help:pms.field_res_partner__margin_days_autoinvoice +#: model:ir.model.fields,help:pms.field_res_users__margin_days_autoinvoice +msgid "Days from Checkout to generate the invoice" +msgstr "Días desde la salida para generar la factura" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_cancelation_rule__apply_on_late +msgid "" +"Days on which the cancelation rule applies when the reason is late arrival. " +"Can be first, all days or specify the days." +msgstr "" +"Días en los que se aplica la regla de cancelación cuando el motivo es " +"llegada tardía. Puede ser primero, todos los días o especificar los días." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_cancelation_rule__apply_on_noshow +msgid "" +"Days on which the cancelation rule applies when the reason is no show. Can " +"be first, all days or specify the days." +msgstr "" +"Días en los que se aplica la regla de cancelación cuando el motivo es no " +"presentado. Puede ser primero, todos los días o especificar los días." + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__days_to_checkin +msgid "Days to Checkin" +msgstr "Días para el Registro de Entrada" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__days_to_checkout +msgid "Days to Checkout" +msgstr "Días para la Salida" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__deduct_down_payments +#: model:ir.model.fields,help:pms.field_folio_advance_payment_inv__deduct_down_payments +msgid "Deduct down payments" +msgstr "Deducir Pagos Iniciales" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__default_invoicing_policy +msgid "Default Invoicing Policy" +msgstr "Política de Facturación por Defecto" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__default_max_avail +msgid "Default Max. Availability" +msgstr "Disponibilidad Máx por defecto" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_res_users__pms_property_id +msgid "Default Property" +msgstr "Hotel por defecto" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__default_quota +msgid "Default Quota" +msgstr "Cupo por defecto" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__default_commission +#: model:ir.model.fields,help:pms.field_res_partner__default_commission +#: model:ir.model.fields,help:pms.field_res_users__default_commission +msgid "Default commission" +msgstr "Comisión por defecto" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__supplier_taxes_id +msgid "Default taxes used when buying the product." +msgstr "Impuestos aplicados por defecto cuando se compra un producto." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__taxes_id +msgid "Default taxes used when selling the product." +msgstr "Impuestos aplicados por defecto cuando se vende un producto." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__uom_id +msgid "Default unit of measure used for all stock operations." +msgstr "" +"Unidad de medida por defecto usada para todas las operaciones de stock." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__uom_po_id +msgid "" +"Default unit of measure used for purchase orders. It must be in the same " +"category as the default unit of measure." +msgstr "" +"Unidad de medida por defecto usada para los pedidos de compra. Debe estar en " +"la misma categoría que las unidades de medida por defecto." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__seller_ids +msgid "Define vendor pricelists." +msgstr "Definir las tarifas del proveedor." + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__trust +msgid "Degree of trust you have in this debtor" +msgstr "Grado de confianza que tiene en este deudor" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__sale_delay +msgid "" +"Delivery lead time, in days. It's the number of days, promised to the " +"customer, between the confirmation of the sales order and the delivery." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__default_departure_hour +#: model:ir.model.fields,field_description:pms.field_pms_reservation__departure_hour +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Departure Hour" +msgstr "Hora de Salida" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__departure_hour +msgid "Departure Hour (HH:MM)" +msgstr "Hora de Salida (GMT)" + +#. module: pms +#: code:addons/pms/models/pms_checkin_partner.py:0 +#, python-format +msgid "Departure date (%s) is prior to arrival on %s" +msgstr "La hora de salida (%s) es anterior a la llegada en %s" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__state__departure_delayed +msgid "Departure delayed" +msgstr "Salidas Hoy" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__route_ids +msgid "" +"Depending on the modules installed, this will allow you to define the route " +"of the product: whether it will be bought, manufactured, replenished on " +"order, etc." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__name +#: model:ir.model.fields,field_description:pms.field_pms_room_type__description +#: model:ir.model.fields,field_description:pms.field_room_closure_reason__description +#: model_terms:ir.ui.view,arch_db:pms.report_folio_document +msgid "Description" +msgstr "Descripción" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__name +msgid "Description of folio sale line" +msgstr "Descripción de las lineas de venta de la ficha" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__description_pickingout +msgid "Description on Delivery Orders" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__description_picking +msgid "Description on Picking" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__description_pickingin +msgid "Description on Receptions" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_amenity_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_form +msgid "Descriptions" +msgstr "Descripciones" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Detail" +msgstr "Detalle" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__credit_card_details +msgid "Details of partner credit card" +msgstr "Detalles de la tarjeta de crédito del partner" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_amenity__active +msgid "Determines if amenity is active" +msgstr "Determina si la característica está activa" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_amenity_type__active +msgid "Determines if amenity type is active" +msgstr "Determina si el tipo de característica están activo" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_cancelation_rule__active +msgid "Determines if cancelation rule is active" +msgstr "Determina si la regla de cancelación está activa" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room__active +msgid "Determines if room is active" +msgstr "Determina si la habitación está activa" + +#. module: pms +#: model:ir.model,name:pms.model_pms_num_rooms_selection +msgid "Dinamic Selection based on avails room" +msgstr "Selección dinámica basada en la disponibilidad" + +#. module: pms +#: model:product.product,name:pms.pms_service_dinner +#: model:product.template,name:pms.pms_service_dinner_product_template +msgid "Dinner" +msgstr "Cena" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_sale_channel__channel_type__direct +msgid "Direct" +msgstr "Directo" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__channel_type_id +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__channel_type_id +msgid "Direct Sale Channel" +msgstr "Canal de Venta Directo" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Disc.%" +msgstr "Desc.%" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__discount +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__discount +msgid "Discount" +msgstr "Descuento" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__discount +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__discount +#: model:ir.model.fields,field_description:pms.field_pms_service_line__discount +msgid "Discount (%)" +msgstr "Descuento" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__discount +msgid "Discount (€)" +msgstr "Descuento (€)" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_service__discount +msgid "Discount (€/ud)" +msgstr "Descuento (€/ud)" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Discount Room" +msgstr "Descuento en la Habitación" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Discount Services" +msgstr "Descuento Servicios" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service_line__discount +msgid "Discount in the price of the service." +msgstr "Descuento en el precio del servicio." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__discount +msgid "Discount of total price" +msgstr "Descuento en el precio total." + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__discount +msgid "Discount of total price in folio sale line" +msgstr "Descuento de precio total en línea de venta de la ficha" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__discount +msgid "Discount of total price in reservation" +msgstr "Decuento del precio total en la reserva" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__discount +#: model:ir.model.fields,help:pms.field_pms_booking_engine__discount +msgid "Discount that be applied in total price" +msgstr "Descuento que se aplicará en el precio total" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_account_analytic_distribution__display_name +#: model:ir.model.fields,field_description:pms.field_account_analytic_line__display_name +#: model:ir.model.fields,field_description:pms.field_account_bank_statement__display_name +#: model:ir.model.fields,field_description:pms.field_account_bank_statement_line__display_name +#: model:ir.model.fields,field_description:pms.field_account_journal__display_name +#: model:ir.model.fields,field_description:pms.field_account_move__display_name +#: model:ir.model.fields,field_description:pms.field_account_move_line__display_name +#: model:ir.model.fields,field_description:pms.field_account_payment__display_name +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__display_name +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__display_name +#: model:ir.model.fields,field_description:pms.field_ir_config_parameter__display_name +#: model:ir.model.fields,field_description:pms.field_ir_http__display_name +#: model:ir.model.fields,field_description:pms.field_ir_pms_property__display_name +#: model:ir.model.fields,field_description:pms.field_mail_compose_message__display_name +#: model:ir.model.fields,field_description:pms.field_payment_acquirer__display_name +#: model:ir.model.fields,field_description:pms.field_payment_transaction__display_name +#: model:ir.model.fields,field_description:pms.field_pms_advanced_filters_wizard__display_name +#: model:ir.model.fields,field_description:pms.field_pms_amenity__display_name +#: model:ir.model.fields,field_description:pms.field_pms_amenity_type__display_name +#: model:ir.model.fields,field_description:pms.field_pms_automated_mails__display_name +#: model:ir.model.fields,field_description:pms.field_pms_availability__display_name +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan__display_name +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__display_name +#: model:ir.model.fields,field_description:pms.field_pms_board_service__display_name +#: model:ir.model.fields,field_description:pms.field_pms_board_service_line__display_name +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type__display_name +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type_line__display_name +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__display_name +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__display_name +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__display_name +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__display_name +#: model:ir.model.fields,field_description:pms.field_pms_folio__display_name +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__display_name +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__display_name +#: model:ir.model.fields,field_description:pms.field_pms_num_rooms_selection__display_name +#: model:ir.model.fields,field_description:pms.field_pms_property__display_name +#: model:ir.model.fields,field_description:pms.field_pms_reservation__display_name +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__display_name +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__display_name +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__display_name +#: model:ir.model.fields,field_description:pms.field_pms_room__display_name +#: model:ir.model.fields,field_description:pms.field_pms_room_type__display_name +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class__display_name +#: model:ir.model.fields,field_description:pms.field_pms_sale_channel__display_name +#: model:ir.model.fields,field_description:pms.field_pms_service__display_name +#: model:ir.model.fields,field_description:pms.field_pms_service_line__display_name +#: model:ir.model.fields,field_description:pms.field_pms_several_partners_wizard__display_name +#: model:ir.model.fields,field_description:pms.field_pms_team_member__display_name +#: model:ir.model.fields,field_description:pms.field_pms_ubication__display_name +#: model:ir.model.fields,field_description:pms.field_pms_wizard_reservation_lines_split__display_name +#: model:ir.model.fields,field_description:pms.field_product_pricelist__display_name +#: model:ir.model.fields,field_description:pms.field_product_pricelist_item__display_name +#: model:ir.model.fields,field_description:pms.field_product_product__display_name +#: model:ir.model.fields,field_description:pms.field_product_template__display_name +#: model:ir.model.fields,field_description:pms.field_res_company__display_name +#: model:ir.model.fields,field_description:pms.field_res_country__display_name +#: model:ir.model.fields,field_description:pms.field_res_partner__display_name +#: model:ir.model.fields,field_description:pms.field_res_partner_category__display_name +#: model:ir.model.fields,field_description:pms.field_res_partner_id_category__display_name +#: model:ir.model.fields,field_description:pms.field_res_partner_id_number__display_name +#: model:ir.model.fields,field_description:pms.field_res_users__display_name +#: model:ir.model.fields,field_description:pms.field_room_closure_reason__display_name +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__display_name +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__display_name +msgid "Display Name" +msgstr "Nombre" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__display_type +msgid "Display Type" +msgstr "Tipo Mostrado" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Doc. Expedition Date/Doc. Validity Date *" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Doc. Number *" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Doc. Type *" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__document_id +msgid "Document" +msgstr "Documento" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "" +"Document\n" +" number" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__document_country_id +msgid "Document Country" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__document_number +msgid "Document Number" +msgstr "Nº de documento" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__document_type +msgid "Document Type" +msgstr "Tipo de Documento" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "Document number" +msgstr "Número de documento" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "Document number:" +msgstr "Número de documento:" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_res_company__document_partner_required +msgid "Document partner required" +msgstr "Documento socio requerido" + +#. module: pms +#: code:addons/pms/models/pms_checkin_partner.py:0 +#, python-format +msgid "Document type and country of document do not match" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_checkin_partner.py:0 +#, python-format +msgid "Document_type has already exists" +msgstr "El tipo de documento ya existe" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_advanced_filters_wizard__pms_domain +msgid "Domain" +msgstr "Dominio" + +#. module: pms +#: model:pms.room.type,name:pms.pms_room_type_double +#: model:product.product,name:pms.pms_room_type_double_product_product +msgid "Double" +msgstr "Doble" + +#. module: pms +#: code:addons/pms/wizards/folio_make_invoice_advance.py:0 +#, python-format +msgid "Down Payment" +msgstr "Adelanto" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__amount +msgid "Down Payment Amount" +msgstr "Cantidad del Adelanto" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__fixed_amount +msgid "Down Payment Amount (Fixed)" +msgstr "Cantidad del Adelanto (Fija)" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__product_id +msgid "Down Payment Product" +msgstr "Producto asociado al Adelanto" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "Down Payments" +msgstr "Adelantos" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__folio_advance_payment_inv__advance_payment_method__fixed +msgid "Down payment (fixed amount)" +msgstr "Adelanto (Cantidad Fija)" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__folio_advance_payment_inv__advance_payment_method__percentage +msgid "Down payment (percentage)" +msgstr "Adelanto (Porcentaje)" + +#. module: pms +#: code:addons/pms/wizards/folio_make_invoice_advance.py:0 +#, python-format +msgid "Down payment of %s%%" +msgstr "Adelanto de %s%%" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__is_downpayment +msgid "" +"Down payments are made when creating invoices from a folio. They are not " +"copied when duplicating a folio." +msgstr "" +"Los adelantos se realizan cuando se crean las facturas de un folio. No serán " +"copiados cuando se duplique un folio." + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.folio_portal_template +msgid "Download" +msgstr "Descargar" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__avoid_simplified_max_amount_downpayment +msgid "Downpayment Invoive without limit amount" +msgstr "Anticipo Invoive sin límite de importe" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_res_company__pms_invoice_downpayment_policy +msgid "Downpayment policy invoce" +msgstr "Facturación de la política de anticipos" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_kanban_view +msgid "Draft" +msgstr "Borrador" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.report_invoice_document +msgid "Draft Invoice" +msgstr "Borrador Factura" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.report_invoice_document +msgid "Draft Simplified Invoice" +msgstr "Borrador Factura Simplificada" + +#. module: pms +#: model:res.partner.id_category,name:pms.document_type_driving_license +msgid "Driving License" +msgstr "Carnét de conducir" + +#. module: pms +#. openerp-web +#: code:addons/pms/static/src/xml/pms_base_templates.xml:0 +#, python-format +msgid "Dropdown menu" +msgstr "Menú Desplegable" + +#. module: pms +#: model:ir.model,name:pms.model_pms_booking_duplicate +msgid "Duplicate Booking" +msgstr "Reserva duplicada" + +#. module: pms +#: code:addons/pms/wizards/pms_booking_duplicate.py:0 +#, python-format +msgid "Duplicate Folios" +msgstr "Folios duplicados" + +#. module: pms +#: code:addons/pms/models/pms_reservation_line.py:0 +#, python-format +msgid "Duplicated reservation line date" +msgstr "Fecha de linea de reserva duplicada" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__email +#: model:ir.model.fields,field_description:pms.field_pms_folio__email +#: model:ir.model.fields,field_description:pms.field_pms_reservation__email +msgid "E-mail" +msgstr "Email" + +#. module: pms +#: model:pms.room.type,name:pms.pms_room_type_economic +#: model:product.product,name:pms.pms_room_type_economic_product_product +msgid "Economic" +msgstr "Económico" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__partner_share +msgid "" +"Either customer (not a user), either shared user. Indicated the current " +"partner is a customer without access or with a limited access created for " +"sharing data." +msgstr "" +"El cliente (no el usuario), o el usuario compartido. Indicó que el cliente " +"actual no tiene acceso o éste es limitado a la creación de datos compartidos." + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__email +#: model:ir.model.fields,field_description:pms.field_res_partner__email +#: model:ir.model.fields,field_description:pms.field_res_users__email +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folio_invitations +msgid "Email" +msgstr "Email" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Email (Optional)" +msgstr "Correo electrónico (Opcional)" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Email Configuration" +msgstr "Configuración de correo electrónico" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Email Templates" +msgstr "Plantillas Correo Electrónico" + +#. module: pms +#: model:ir.model,name:pms.model_mail_compose_message +msgid "Email composition wizard" +msgstr "Asistente de redacción de correo electrónico" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__employee +msgid "Employee" +msgstr "Empleado/a" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_product_pricelist_item__date_end_consumption +msgid "End Date Consumption" +msgstr "Fin Noche Consumición" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_engine__end_date +msgid "End date for creation of reservations and folios" +msgstr "End date for creation of reservations and folios" + +#. module: pms +#: model:ir.model.fields,help:pms.field_product_pricelist_item__date_end_consumption +msgid "End date to apply daily pricelist items" +msgstr "Fecha límite para aplicar los ítems de la tarifa diaria" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__tracking +msgid "Ensure the traceability of a storable product in your warehouse." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__arrival +msgid "Enter" +msgstr "Entrada" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "Entry date" +msgstr "Fecha de entrada" + +#. module: pms +#: code:addons/pms/models/pms_checkin_partner.py:0 +#, python-format +msgid "" +"Error when evaluating the id_category validation code::\n" +" %s \n" +"(%s)" +msgstr "" +"Error al evaluar el código de validación de id_category::\n" +" %s \n" +"(%s)" + +#. module: pms +#: model:res.partner.id_category,name:pms.document_type_european_residence +msgid "European Residence permit" +msgstr "Permiso de residencia europeo" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__checkin_datetime +msgid "Exact Arrival" +msgstr "Llegada Exacta" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__checkout_datetime +msgid "Exact Departure" +msgstr "Salida exacta" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__departure +msgid "Exit" +msgstr "Salida" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__property_exit_template +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Exit Email" +msgstr "Correo Electrónico de Salida" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "Exit date" +msgstr "Fecha Salida" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "" +"Expedition\n" +" date" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__document_expedition_date +msgid "Expedition Date" +msgstr "Fecha de expedición" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__property_account_expense_id +msgid "Expense Account" +msgstr "Cuenta de Gastos" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__expense_policy +msgid "" +"Expenses and vendor bills can be re-invoiced to a customer.With this option, " +"a validated expense can be re-invoice to a customer at its cost or sales " +"price." +msgstr "" +"Los gastos y las tarifas del proveedor pueden ser refacturadas al cliente. " +"Con esta opción, un gasto validado puede ser refacturado al cliente a su " +"coste o precio de venta ." + +#. module: pms +#: model:ir.model.fields,help:pms.field_room_closure_reason__description +msgid "Explanation of the reason for closing a room" +msgstr "Explicación del motivo de cierre de una habitación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__external_reference +#: model:ir.model.fields,field_description:pms.field_pms_reservation__external_reference +msgid "External Reference" +msgstr "Referencia Externa" + +#. module: pms +#: model:product.product,name:pms.pms_service_extra_bed +#: model:product.template,name:pms.pms_service_extra_bed_product_template +msgid "Extra Bed" +msgstr "Cama Supletoria" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room__extra_beds_allowed +msgid "Extra Beds Allowed" +msgstr "Camas Supletorias Permtidas" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Extra Service" +msgstr "Servicio Extra" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "Extra Services" +msgstr "Servicios Extras" + +#. module: pms +#: code:addons/pms/models/pms_room.py:0 +#, python-format +msgid "Extra beds can't be greater than allowed beds for this room" +msgstr "" +"No puede haber más camas supletorias que la cantidad de camas permitidas " +"para esta habitación." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__partner_relationship +msgid "Family relationship between travelers" +msgstr "Relación familiar entre viajeros" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_checkin_partner__gender__female +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Female" +msgstr "Mujer" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_ir_pms_property__field_id +msgid "Field" +msgstr "Campo" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__cancelled_reason +msgid "" +"Field indicating type of cancellation. It can be 'late', 'intime' or 'noshow'" +msgstr "" +"Campo que indica el tipo de cancelación. Puede ser 'tarde', 'a tiempo' o 'no " +"presentarse'" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__user_ids +msgid "Field related to res.users. Allowed users on the property" +msgstr "" +"Campo relacionado con res.usuarios. Usuarios permitidos en la propiedad" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__splitted +msgid "" +"Field that indicates if the reservation is split. A reservation is split " +"when guests don't sleep in the same room every night" +msgstr "" +"Campo que indica si la reserva está fraccionada. Una reserva se divide " +"cuando los huéspedes no duermen en la misma habitación todas las noches." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__ratio_checkin_data +msgid "" +"Field that stores the number of checkin partners pending to checkin (with " +"the state = draft)" +msgstr "Campo que almacena el número de clientes pendientes por hacer check-in" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__avoid_mails +msgid "Field to indicate not sent mail comunications" +msgstr "Campo para indicar comunicaciones de correo no enviadas" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__reservation_order +msgid "Field to order by reservation id" +msgstr "Campo para ordenar por id de reserva" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__date_order +msgid "Field to order by service" +msgstr "Campo para ordenar por servicio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__service_order +msgid "Field to order by service id" +msgstr "Campo para ordenar por id de servicio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type_class__sequence +msgid "" +"Field used to change the position of the room type classes in tree view." +msgstr "" +"Campo utilizado para cambiar la posición de las clases de tipo de habitación " +"en la vista de árbol." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__sequence +msgid "Field used to change the position of the room types in tree view." +msgstr "" +"Campo utilizado para cambiar la posición de los tipos de habitación en la " +"vista de árbol." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room__sequence +msgid "" +"Field used to change the position of the rooms in tree view.Changing the " +"position changes the sequence" +msgstr "" +"Campo utilizado para cambiar la posición de las habitaciones en la vista de " +"árbol. Cambiar la posición cambia la secuencia" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_ubication__sequence +msgid "" +"Field used to change the position of the ubications in tree view.Changing " +"the position changes the sequence" +msgstr "" +"Campo utilizado para cambiar la posición de las ubicaciones en la vista de " +"árbol. Cambiar la posición cambia la secuencia" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__checkin_sequence_id +msgid "Field used to create the name of the checkin partner" +msgstr "Campo utilizado para crear el nombre del cliente" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.advanced_filters_wizard +msgid "Filters" +msgstr "Filtros" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__price_day_total +msgid "Final price" +msgstr "Precio Final" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_cancelation_rule__apply_on_late__first +#: model:ir.model.fields.selection,name:pms.selection__pms_cancelation_rule__apply_on_noshow__first +msgid "First Day" +msgstr "Primer Día" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__first_checkin +msgid "First Folio Checkin" +msgstr "Primer Registro de Folio" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__firstname +msgid "First Name" +msgstr "Nombre" + +#. module: pms +#: model:pms.ubication,name:pms.pms_ubication_1st_floor +msgid "First floor" +msgstr "Primer piso" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__firstname +#: model:ir.model.fields,field_description:pms.field_res_partner__firstname +#: model:ir.model.fields,field_description:pms.field_res_users__firstname +msgid "First name" +msgstr "Nombre" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__fiscal_position_id +#: model:ir.model.fields,field_description:pms.field_pms_property__property_account_position_id +msgid "Fiscal Position" +msgstr "Posición Fiscal" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_ir_pms_property__value_float +msgid "Float Field Value" +msgstr "Valor de Campo Flotante" + +#. module: pms +#: model:ir.actions.act_window,name:pms.open_pms_folio1_form_tree_all +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__folio_id +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__folio_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__folio_id +#: model:ir.model.fields,field_description:pms.field_pms_service__folio_id +#: model:ir.model.fields,field_description:pms.field_pms_several_partners_wizard__folio_id +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__folio_id +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__folio_id +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Folio" +msgstr "Ficha" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folio +msgid "Folio #" +msgstr "Ficha" + +#. module: pms +#: model:ir.model,name:pms.model_folio_advance_payment_inv +msgid "Folio Advance Payment Invoice" +msgstr "Facturas de Pago Adelantados de la Ficha" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_folio_changes +#: model:ir.model,name:pms.model_wizard_folio_changes +#: model_terms:ir.ui.view,arch_db:pms.wizard_folio_changes_view_form +msgid "Folio Changes" +msgstr "Modificar Ficha" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Folio Form" +msgstr "Forma de la Ficha" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_account_move_line__folio_line_ids +msgid "Folio Lines" +msgstr "Líneas de la Ficha" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__name +msgid "Folio Number" +msgstr "Número de la Ficha" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_tree +msgid "Folio Pending Amount" +msgstr "Cantidad pendiente de la Ficha" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__folio_id +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__reference_folio_id +msgid "Folio Reference" +msgstr "Referencia de la Ficha" + +#. module: pms +#: model:ir.model,name:pms.model_folio_sale_line +msgid "Folio Sale Line" +msgstr "Línea de Venta de la Ficha" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__folio_sequence_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__folio_sequence +msgid "Folio Sequence" +msgstr "Nº de Ficha" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__state +msgid "Folio Status" +msgstr "Estado de la Ficha" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__booking_engine_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__booking_duplicate_id +msgid "Folio Wizard ID" +msgstr "Asistente de la Ficha" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__note +msgid "Folio billing terms and conditions" +msgstr "Términos y condiciones" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_booking_duplicate +#: model:ir.actions.act_window,name:pms.action_booking_engine +msgid "Folio creation" +msgstr "Creación de la Ficha" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_engine__folio_id +msgid "Folio in which are included new reservations" +msgstr "Ficha en la que se incluyen nuevas reservas" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__folio_id +msgid "Folio in which the service is included" +msgstr "Ficha en la que se incluye el servicio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__move_ids +msgid "Folio invoices related to account move." +msgstr "Facturas de folio relacionadas con el movimiento de la cuenta." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__name +msgid "" +"Folio name. When creating a folio the name is automatically formed with a " +"sequence" +msgstr "" +"Nombre del folio. Al crear un folio el nombre se forma automáticamente con " +"una secuencia" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__payment_multi +msgid "Folio paid with payments assigned to other folios" +msgstr "Folio pagado con pagos asignados a otros folios" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__invoice_lines +msgid "Folio sale line invoice lines" +msgstr "Folio línea de venta líneas de factura" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__state +msgid "" +"Folio status; it can be Quotation, Quotation Sent, Confirmed, Locked or " +"Cancelled" +msgstr "" +"Estado del folio; puede ser Cotización, Cotización enviada, Confirmada, " +"Bloqueada o Cancelada" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__reference_folio_id +msgid "Folio to copy data" +msgstr "Folio para copiar datos" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__folio_id +msgid "Folio to which folio sale line belongs" +msgstr "Folio al que pertenece la línea de venta" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__folio_id +msgid "Folio to which reservation of checkin partner belongs" +msgstr "Folio al que pertenece la reserva del socio de facturación" + +#. module: pms +#: model:ir.actions.act_window,name:pms.pms_partner_folios +#: model:ir.model.fields,field_description:pms.field_account_bank_statement_line__folio_ids +#: model:ir.model.fields,field_description:pms.field_account_move__folio_ids +#: model:ir.model.fields,field_description:pms.field_account_move_line__folio_ids +#: model:ir.model.fields,field_description:pms.field_account_payment__folio_ids +#: model:ir.model.fields,field_description:pms.field_payment_transaction__folio_ids +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__created_folio_ids +#: model:ir.model.fields,field_description:pms.field_pms_property__pms_folio_ids +#: model:ir.model.fields,field_description:pms.field_res_partner__pms_folio_ids +#: model:ir.model.fields,field_description:pms.field_res_users__pms_folio_ids +#: model:ir.ui.menu,name:pms.pms_folio_menu +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_pivot +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folio +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folios +#: model_terms:ir.ui.view,arch_db:pms.portal_my_home_menu_folio +#: model_terms:ir.ui.view,arch_db:pms.res_partner_view_form +msgid "Folios" +msgstr "Folios" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__created_folio_ids +msgid "Folios already created" +msgstr "Folios ya creados" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.res_partner_view_form +msgid "Folios related with this contact" +msgstr "Fichas relacionadas con este contacto" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_move__folio_ids +msgid "Folios where the account move are included" +msgstr "Folios en los que se incluye el movimiento de la cuenta" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_home_menu_folio +msgid "Folios/" +msgstr "Fichas/" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__message_follower_ids +#: model:ir.model.fields,field_description:pms.field_pms_folio__message_follower_ids +#: model:ir.model.fields,field_description:pms.field_pms_property__message_follower_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__message_follower_ids +#: model:ir.model.fields,field_description:pms.field_pms_room_type__message_follower_ids +msgid "Followers" +msgstr "Seguidores" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__message_channel_ids +#: model:ir.model.fields,field_description:pms.field_pms_folio__message_channel_ids +#: model:ir.model.fields,field_description:pms.field_pms_property__message_channel_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__message_channel_ids +#: model:ir.model.fields,field_description:pms.field_pms_room_type__message_channel_ids +msgid "Followers (Channels)" +msgstr "Seguidores (Canales)" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__message_partner_ids +#: model:ir.model.fields,field_description:pms.field_pms_folio__message_partner_ids +#: model:ir.model.fields,field_description:pms.field_pms_property__message_partner_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__message_partner_ids +#: model:ir.model.fields,field_description:pms.field_pms_room_type__message_partner_ids +msgid "Followers (Partners)" +msgstr "Seguidores (Clientes)" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__activity_type_icon +#: model:ir.model.fields,help:pms.field_pms_folio__activity_type_icon +#: model:ir.model.fields,help:pms.field_pms_property__activity_type_icon +#: model:ir.model.fields,help:pms.field_pms_reservation__activity_type_icon +#: model:ir.model.fields,help:pms.field_pms_room_type__activity_type_icon +msgid "Font awesome icon e.g. fa-tasks" +msgstr "Icono de fuente impresionante, por ejemplo fa-tasks" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_account_journal__allowed_pms_payments +msgid "For manual payments" +msgstr "Para los pagos manuales" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_service__no_auto_add_lines +msgid "Force No Auto Add Lines" +msgstr "Forzar a No Añadir Líneas Automáticamente" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__force_nothing_to_invoice +msgid "Force no invoice" +msgstr "Forzar a no facturar" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__virtual_available +msgid "Forecast Quantity" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__virtual_available +msgid "" +"Forecast quantity (computed as Quantity On Hand - Outgoing + Incoming)\n" +"In a context with a single Stock Location, this includes goods stored in " +"this location, or any of its children.\n" +"In a context with a single Warehouse, this includes goods stored in the " +"Stock Location of this Warehouse, or any of its children.\n" +"Otherwise, this includes goods stored in any Stock Location with 'internal' " +"type." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__free_qty +msgid "" +"Forecast quantity (computed as Quantity On Hand - reserved quantity)\n" +"In a context with a single Stock Location, this includes goods stored in " +"this location, or any of its children.\n" +"In a context with a single Warehouse, this includes goods stored in the " +"Stock Location of this Warehouse, or any of its children.\n" +"Otherwise, this includes goods stored in any Stock Location with 'internal' " +"type." +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_property.py:0 +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Format Arrival Hour (HH:MM) Error: %s" +msgstr "Formato de la Hora de Llegada (HH:MM) Error:%s" + +#. module: pms +#: code:addons/pms/models/pms_property.py:0 +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Format Departure Hour (HH:MM) Error: %s" +msgstr "Formato de la Hora de Salida (HH:MM) Error:%s" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__email_formatted +msgid "Format email address \"Name \"" +msgstr "" +"Formato de las direcciones de correo electrónico \"Nombre \"" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__email_formatted +msgid "Formatted Email" +msgstr "Email Formateado" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room__short_name +msgid "" +"Four character name, if not set, autocompletes with the first two letters of " +"the room name and two incremental numbers" +msgstr "" +"Nombre de cuatro caracteres, si no se establece, se autocompleta con las dos " +"primeras letras del nombre de la habitación y dos números incrementales" + +#. module: pms +#: model:pms.ubication,name:pms.pms_ubication_4st_floor +msgid "Fourth floor" +msgstr "Cuarto piso" + +#. module: pms +#: model:product.product,name:pms.pms_service_free_bar +#: model:product.template,name:pms.pms_service_free_bar_product_template +msgid "Free Bar" +msgstr "Barra Libre" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__free_qty +msgid "Free To Use Quantity " +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.wizard_folio_changes_view_form +msgid "Friday" +msgstr "Viernes" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__start_date +msgid "From" +msgstr "Desde" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__res_partner__invoicing_policy__checkout +msgid "From Checkout" +msgstr "Desde la salida" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__start_date +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__start_date +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__checkin +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__checkin +msgid "From:" +msgstr "Desde:" + +#. module: pms +#: model:pms.board.service,name:pms.pms_board_service_full_board +msgid "Full Board" +msgstr "Pensión Completa" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__folio_sale_line__invoice_status__invoiced +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__invoice_status__invoiced +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__invoice_status__invoiced +#: model:ir.model.fields.selection,name:pms.selection__pms_service__invoice_status__invoiced +msgid "Fully Invoiced" +msgstr "Totalmente Facturada" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +msgid "Future" +msgstr "Futuro" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__gender +#: model:ir.model.fields,field_description:pms.field_pms_property__gender +#: model:ir.model.fields,field_description:pms.field_res_partner__gender +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "Gender" +msgstr "Género" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Gender *" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "General Info" +msgstr "Info General" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "General Information" +msgstr "Información General" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan_rule__quota +#: model:ir.model.fields,help:pms.field_pms_massive_changes_wizard__quota +msgid "Generic Quota assigned." +msgstr "Cupo Genérico Asignada." + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__partner_latitude +msgid "Geo Latitude" +msgstr "Latitud Geográfica" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__partner_longitude +msgid "Geo Longitude" +msgstr "Longitud Geofráfica" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_folio_view_tree +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_reservation_view_tree +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_tree +msgid "Get in" +msgstr "Llega" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__board_price +#: model:ir.model.fields,help:pms.field_product_product__board_price +msgid "Get price on board service" +msgstr "Regímenes incluidos en el Servicio de Habitación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_line__price_day_total +msgid "Get the price with discount applied" +msgstr "Obtener el precio con descuento aplicado" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__packaging_ids +msgid "Gives the different ways to package the same product." +msgstr "Ofrece diferentes formas de empaquetar el mismo producto." + +#. module: pms +#: model:pms.room.type,name:pms.demo_pms_room_type_grand_suite +#: model:product.product,name:pms.demo_pms_room_type_grand_suite_product_product +msgid "Grand suite" +msgstr "Suite principal" + +#. module: pms +#: model:pms.ubication,name:pms.pms_ubication_ground_floor +msgid "Ground floor" +msgstr "Planta baja" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "Group By" +msgstr "Agrupar Por" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_amenity_search +#: model_terms:ir.ui.view,arch_db:pms.pms_service_view_search +msgid "Group By..." +msgstr "Agrupar Por..." + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folio_invitations +msgid "Guest" +msgstr "Invitado/a" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__partner_requests +msgid "Guest requests" +msgstr "Peticiones del Cliente" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.folio_portal_template +msgid "Guests to Precheckin" +msgstr "Invitados a facturación previa" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__checkin_partner_ids +msgid "Guests who will occupy the room" +msgstr "Huéspedes de la habitación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__default_arrival_hour +#: model:ir.model.fields,help:pms.field_pms_property__default_departure_hour +msgid "HH:mm Format" +msgstr "Formato HH:mm" + +#. module: pms +#: model:ir.model,name:pms.model_ir_http +msgid "HTTP Routing" +msgstr "Ruta HTTP " + +#. module: pms +#: model:pms.amenity,name:pms.pms_amenity_hair_dryer +msgid "Hair Dryer" +msgstr "Secador de Pelo" + +#. module: pms +#: model:pms.board.service,name:pms.pms_board_service_half_board +msgid "Half Board" +msgstr "Media Pensión" + +#. module: pms +#: model:pms.amenity,name:pms.pms_amenity_half_sized_refrigerator +msgid "Half-sized Refrigerator" +msgstr "Nevera Mediana" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__show_update_pricelist +msgid "Has Pricelist Changed" +msgstr "Regla de Tarifa" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__has_unreconciled_entries +msgid "Has Unreconciled Entries" +msgstr "Tiene entradas no conciliadas" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__has_down_payments +#: model:ir.model.fields,help:pms.field_folio_advance_payment_inv__has_down_payments +msgid "Has down payments" +msgstr "Tiene Pagos Iniciales" + +#. module: pms +#: model:mail.template,subject:pms.precheckin_invitation_email +msgid "" +"Hi ${object.firstname}, do your check-in now in ${object.pms_property_id." +"name}" +msgstr "" +"Hola ${object.firstname}, haz tu check-in ahora en ${object.pms_property_id." +"name}" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation_precheckin +msgid "Hi," +msgstr "Hola," + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +msgid "Hide Strangers" +msgstr "Ocultar a extraños" + +#. module: pms +#: model:pms.amenity,name:pms.pms_amenity_high_speed_wired_internet_connection +msgid "High speed Wired Internet access" +msgstr "Acceso a internet por cable de alta velocidad" + +#. module: pms +#: model:pms.amenity,name:pms.pms_amenity_high_quality_shampoo_and_sopa_essential_herbs +msgid "High-quality Shampoo and Soap Essential Herbs" +msgstr "Champú y Jabón de Esencias de Hiebas de Alta Calidad" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.folio_portal_template +msgid "History" +msgstr "Historia" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_prechekin_reservation +msgid "Host" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__document_number +msgid "Host document number" +msgstr "Número de documento de cliente" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_kanban_view +msgid "Hosted's Name" +msgstr "Nombre del huésped" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_graph +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_pivot +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_calendar +msgid "Hosts" +msgstr "Huéspedes" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_account_analytic_distribution__id +#: model:ir.model.fields,field_description:pms.field_account_analytic_line__id +#: model:ir.model.fields,field_description:pms.field_account_bank_statement__id +#: model:ir.model.fields,field_description:pms.field_account_bank_statement_line__id +#: model:ir.model.fields,field_description:pms.field_account_journal__id +#: model:ir.model.fields,field_description:pms.field_account_move__id +#: model:ir.model.fields,field_description:pms.field_account_move_line__id +#: model:ir.model.fields,field_description:pms.field_account_payment__id +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__id +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__id +#: model:ir.model.fields,field_description:pms.field_ir_config_parameter__id +#: model:ir.model.fields,field_description:pms.field_ir_http__id +#: model:ir.model.fields,field_description:pms.field_ir_pms_property__id +#: model:ir.model.fields,field_description:pms.field_mail_compose_message__id +#: model:ir.model.fields,field_description:pms.field_payment_acquirer__id +#: model:ir.model.fields,field_description:pms.field_payment_transaction__id +#: model:ir.model.fields,field_description:pms.field_pms_advanced_filters_wizard__id +#: model:ir.model.fields,field_description:pms.field_pms_amenity__id +#: model:ir.model.fields,field_description:pms.field_pms_amenity_type__id +#: model:ir.model.fields,field_description:pms.field_pms_automated_mails__id +#: model:ir.model.fields,field_description:pms.field_pms_availability__id +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan__id +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__id +#: model:ir.model.fields,field_description:pms.field_pms_board_service__id +#: model:ir.model.fields,field_description:pms.field_pms_board_service_line__id +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type__id +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type_line__id +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__id +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__id +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__id +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__id +#: model:ir.model.fields,field_description:pms.field_pms_folio__id +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__id +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__id +#: model:ir.model.fields,field_description:pms.field_pms_num_rooms_selection__id +#: model:ir.model.fields,field_description:pms.field_pms_property__id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__id +#: model:ir.model.fields,field_description:pms.field_pms_room__id +#: model:ir.model.fields,field_description:pms.field_pms_room_type__id +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class__id +#: model:ir.model.fields,field_description:pms.field_pms_sale_channel__id +#: model:ir.model.fields,field_description:pms.field_pms_service__id +#: model:ir.model.fields,field_description:pms.field_pms_service_line__id +#: model:ir.model.fields,field_description:pms.field_pms_several_partners_wizard__id +#: model:ir.model.fields,field_description:pms.field_pms_team_member__id +#: model:ir.model.fields,field_description:pms.field_pms_ubication__id +#: model:ir.model.fields,field_description:pms.field_pms_wizard_reservation_lines_split__id +#: model:ir.model.fields,field_description:pms.field_product_pricelist__id +#: model:ir.model.fields,field_description:pms.field_product_pricelist_item__id +#: model:ir.model.fields,field_description:pms.field_product_product__id +#: model:ir.model.fields,field_description:pms.field_product_template__id +#: model:ir.model.fields,field_description:pms.field_res_company__id +#: model:ir.model.fields,field_description:pms.field_res_country__id +#: model:ir.model.fields,field_description:pms.field_res_partner__id +#: model:ir.model.fields,field_description:pms.field_res_partner_category__id +#: model:ir.model.fields,field_description:pms.field_res_partner_id_category__id +#: model:ir.model.fields,field_description:pms.field_res_partner_id_number__id +#: model:ir.model.fields,field_description:pms.field_res_users__id +#: model:ir.model.fields,field_description:pms.field_room_closure_reason__id +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__id +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__id +msgid "ID" +msgstr "Identificación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_res_partner_id_number__name +msgid "ID Number" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_res_partner_id_number__category_id +msgid "ID type defined in configuration. For example, Driver License" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__im_status +msgid "IM Status" +msgstr "Estado del IM" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__activity_exception_icon +#: model:ir.model.fields,field_description:pms.field_pms_folio__activity_exception_icon +#: model:ir.model.fields,field_description:pms.field_pms_property__activity_exception_icon +#: model:ir.model.fields,field_description:pms.field_pms_reservation__activity_exception_icon +#: model:ir.model.fields,field_description:pms.field_pms_room_type__activity_exception_icon +msgid "Icon" +msgstr "Icono" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__activity_exception_icon +#: model:ir.model.fields,help:pms.field_pms_folio__activity_exception_icon +#: model:ir.model.fields,help:pms.field_pms_property__activity_exception_icon +#: model:ir.model.fields,help:pms.field_pms_reservation__activity_exception_icon +#: model:ir.model.fields,help:pms.field_pms_room_type__activity_exception_icon +msgid "Icon to indicate an exception activity." +msgstr "Icono para indicar una actividad excepcional." + +#. module: pms +#: model:res.partner.id_category,name:pms.document_type_identification_document +msgid "Identification Document" +msgstr "Documento de identificación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__id_numbers +msgid "Identification Numbers" +msgstr "Números de documentos" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__default_code +msgid "Identification code for a room type" +msgstr "Código de identificación del tipo de habitación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__aeat_identification +msgid "Identification for AEAT purposes" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__identifier +msgid "Identifier" +msgstr "Identificador" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "" +"If a Folio is done, you cannot modify it manually anymore. However, you will " +"still be able to invoice. This is used to freeze the Folio." +msgstr "" +"Si un Folio está terminado, ya no puede modificarlo manualmente. Sin " +"embargo, aún podrá facturar. Esto se utiliza para congelar el Folio." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__message_needaction +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__message_unread +#: model:ir.model.fields,help:pms.field_pms_folio__message_needaction +#: model:ir.model.fields,help:pms.field_pms_folio__message_unread +#: model:ir.model.fields,help:pms.field_pms_property__message_needaction +#: model:ir.model.fields,help:pms.field_pms_property__message_unread +#: model:ir.model.fields,help:pms.field_pms_reservation__message_needaction +#: model:ir.model.fields,help:pms.field_pms_reservation__message_unread +#: model:ir.model.fields,help:pms.field_pms_room_type__message_needaction +#: model:ir.model.fields,help:pms.field_pms_room_type__message_unread +msgid "If checked, new messages require your attention." +msgstr "Si está marcado, los mensajes nuevos requieren su atención." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__message_has_error +#: model:ir.model.fields,help:pms.field_pms_folio__message_has_error +#: model:ir.model.fields,help:pms.field_pms_property__message_has_error +#: model:ir.model.fields,help:pms.field_pms_reservation__message_has_error +#: model:ir.model.fields,help:pms.field_pms_room_type__message_has_error +msgid "If checked, some messages have a delivery error." +msgstr "Si está marcado, algunos mensajes tienen un error de entrega." + +#. module: pms +#: model:ir.model.fields,help:pms.field_wizard_folio_changes__apply_day_qty +msgid "If not set, it will use the default product day qty" +msgstr "" +"Si no se define, se utilizará la cantidad diaria de productos por defecto" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__team_id +msgid "" +"If set, this Sales Team will be used for sales and assignments related to " +"this partner" +msgstr "" +"Si está configurado, este Equipo de Ventas se utilizará para ventas y " +"asignaciones relacionadas con este cliente" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__is_blacklisted +msgid "" +"If the email address is on the blacklist, the contact won't receive mass " +"mailing anymore, from any list" +msgstr "" +"Si la dirección de email está en la lista negra, el contacto no recibirá " +"correos masivos de ninguna lista" + +#. module: pms +#: model:ir.model.fields,help:pms.field_product_pricelist__is_pms_available +msgid "If the pricelist is available in the PMS" +msgstr "Si la lista de precios está disponible en el PMS" + +#. module: pms +#: code:addons/pms/models/product_pricelist.py:0 +#, python-format +msgid "" +"If the pricelist is available in the PMS, you must select an availability " +"plan" +msgstr "" +"Si la lista de precios está disponible en el PMS, debe seleccionar un plan " +"de disponibilidad" + +#. module: pms +#: model:ir.model.fields,help:pms.field_res_company__document_partner_required +msgid "" +"If true, the partner document is required\n" +" to create a new contact" +msgstr "" +"Si es verdadero, se requiere el documento del interlocutor\n" +" para crear un nuevo contacto" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan__active +msgid "" +"If unchecked, it will allow you to hide the Availability plan without " +"removing it." +msgstr "" +"Si no está seleccionado, se permitirá ocultar el plan de disponibilidad sin " +"eliminarlo." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__active +msgid "" +"If unchecked, it will allow you to hide the product without removing it." +msgstr "" +"Si no está seleccionado, se permitirá ocultar el plan de disponibilidad sin " +"eliminarlo." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type_class__active +msgid "If unchecked, it will allow you to hide the room type" +msgstr "" +"Si no está seleccionado, se permitirá ocultar el plan de disponibilidad sin " +"eliminarlo" + +#. module: pms +#: code:addons/pms/controllers/pms_portal.py:0 +#, python-format +msgid "" +"If we store your payment information on our server, subscription payments " +"will be made automatically." +msgstr "" +"Si almacenamos su información de pago en nuestro servidor, los pagos de la " +"suscripción se realizarán automáticamente." + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation_precheckin +msgid "" +"If you wish, you can share with the rest of the guests the access to their " +"check-in so that they can fill it out.
" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__image_128 +#: model:ir.model.fields,field_description:pms.field_pms_property__image_1920 +#: model:ir.model.fields,field_description:pms.field_pms_room_type__image_1920 +msgid "Image" +msgstr "Imagen" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__image_1024 +#: model:ir.model.fields,field_description:pms.field_pms_room_type__image_1024 +msgid "Image 1024" +msgstr "Imagen 1024" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__image_128 +#: model:ir.model.fields,field_description:pms.field_pms_room_type__image_128 +msgid "Image 128" +msgstr "Imagen 128" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__image_256 +#: model:ir.model.fields,field_description:pms.field_pms_room_type__image_256 +msgid "Image 256" +msgstr "Imagen 256" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__image_512 +#: model:ir.model.fields,field_description:pms.field_pms_room_type__image_512 +msgid "Image 512" +msgstr "Imagen 512" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__logo +msgid "Image in checkin" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__product_image +msgid "Image of the service" +msgstr "Causa de estar Fuera de Servicio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__standard_price +msgid "" +"In Standard Price & AVCO: value of the product (automatically computed in " +"AVCO).\n" +" In FIFO: value of the next unit that will leave the stock " +"(automatically computed).\n" +" Used to value the product when the purchase cost is not known (e.g. " +"inventory adjustment).\n" +" Used to compute margins on sale orders." +msgstr "" +"En Precio estándar y AVCO: valor del producto (calculado automáticamente en " +"AVCO).\n" +" En FIFO: valor de la siguiente unidad que saldrá de las existencias " +"(calculado automáticamente).\n" +" Se utiliza para valorar el producto cuando no se conoce el coste de " +"compra (por ejemplo, ajuste de inventario).\n" +" Se utiliza para calcular los márgenes de los pedidos de venta." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__partner_name +msgid "In the name of whom the reservation is made" +msgstr "A nombre de quien se hace la reserva" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__cancelled_reason__intime +msgid "In time" +msgstr "A tiempo" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__partner_name +#: model:ir.model.fields,help:pms.field_pms_booking_engine__partner_name +msgid "In whose name is the reservation" +msgstr "A nombre de quién está la reserva" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__service_ids +msgid "Included services in the reservation" +msgstr "Servicios incluidos en la reserva" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__deposit_account_id +#: model:ir.model.fields,field_description:pms.field_pms_room_type__property_account_income_id +msgid "Income Account" +msgstr "Cuenta de Ingresos" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__incoming_qty +msgid "Incoming" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_checkin_partner__state__draft +msgid "Incomplete data" +msgstr "Datos incompletos" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__overbooking +msgid "Indicate if exists overbooking" +msgstr "Indica si existe overbooking" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_line__overbooking +msgid "Indicate if exists overbooking in the reservation line" +msgstr "Indicar si existe sobreventa en la línea de reserva" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__is_reselling +msgid "Indicate if exists reselling in any reservation line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan_rule__closed +msgid "Indicate if property is closed or not" +msgstr "Indicates if the property is closed or not" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__cancelled_reason +msgid "Indicates cause of cancelled" +msgstr "Causa de la Cancelación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__daily_limit +#: model:ir.model.fields,help:pms.field_product_product__daily_limit +#: model:ir.model.fields,help:pms.field_product_template__daily_limit +msgid "Indicates how much products can consumed in one day" +msgstr "Indica cuántos productos se pueden consumir en un día" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__invoice_to_agency +#: model:ir.model.fields,help:pms.field_res_partner__invoice_to_agency +#: model:ir.model.fields,help:pms.field_res_users__invoice_to_agency +msgid "Indicates if agency invoices partner" +msgstr "Indica si la agencia factura al cliente" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__invoice_to_agency +msgid "" +"Indicates if agency invoices partner\n" +" (it only affects those nights/services sold through the agency)" +msgstr "" +"Indica si la agencia factura al socio\n" +" (sólo afecta a las noches/servicios vendidos a través de la agencia)" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__apply_pricelist +#: model:ir.model.fields,help:pms.field_res_partner__apply_pricelist +#: model:ir.model.fields,help:pms.field_res_users__apply_pricelist +msgid "Indicates if agency pricelist is applied to his reservations" +msgstr "Indica si se aplica tarifa de agencia a sus reservas" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__per_day +msgid "Indicates if service is sold by days" +msgstr "Indica si el servicio se vende por días" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__is_crib +#: model:ir.model.fields,help:pms.field_product_product__is_crib +#: model:ir.model.fields,help:pms.field_product_template__is_crib +msgid "Indicates if that product is a crib" +msgstr "Indica si ese producto es una cuna" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__is_extra_bed +#: model:ir.model.fields,help:pms.field_product_product__is_extra_bed +#: model:ir.model.fields,help:pms.field_product_template__is_extra_bed +msgid "Indicates if that product is a extra bed, add +1 capacity in the room" +msgstr "" +"Indica si ese producto es una cama supletoria, añade +1 de capacidad en la " +"habitación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__is_pms_available +#: model:ir.model.fields,help:pms.field_product_product__is_pms_available +#: model:ir.model.fields,help:pms.field_product_template__is_pms_available +msgid "Indicates if that product is available in PMS" +msgstr "Indica si ese producto está disponible en PMS" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__is_agency +#: model:ir.model.fields,help:pms.field_res_partner__is_agency +#: model:ir.model.fields,help:pms.field_res_users__is_agency +msgid "Indicates if the partner is an agency" +msgstr "Indica si el partner es una agencia" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__blocked +msgid "Indicates if the reservation is blocked" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_line__is_reselling +msgid "Indicates if the reservation line is reselling" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_sale_channel__is_on_line +msgid "Indicates if the sale channel is on-line" +msgstr "Indica si el canal de venta es online" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__is_board_service +msgid "" +"Indicates if the service included in folio sale line is part of a board " +"service" +msgstr "" +"Indica si el servicio incluido en la línea de venta de la ficha es parte de " +"un board service" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__is_cancel_penalty +msgid "Indicates if the service is a cancel penalty" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__is_board_service +msgid "Indicates if the service is part of a board service" +msgstr "Indica si el servicio es parte de un board service" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service_line__is_board_service +msgid "Indicates if the service line is part of a board service" +msgstr "Indica si la línea de servicio es parte de un board service" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service_room_type__by_default +msgid "Indicates if this board service is applied by default in the room type" +msgstr "" +"Indica si este board service se aplica por defecto en el tipo de habitación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__per_day +#: model:ir.model.fields,help:pms.field_product_product__per_day +#: model:ir.model.fields,help:pms.field_product_template__per_day +msgid "Indicates that the product is sold by days" +msgstr "Precio cuando el producto es vendido a los clientes" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__per_person +#: model:ir.model.fields,help:pms.field_product_product__per_person +#: model:ir.model.fields,help:pms.field_product_template__per_person +msgid "Indicates that the product is sold per person" +msgstr "Precio cuando el producto es vendido a los clientes" + +#. module: pms +#: model:ir.model.fields,help:pms.field_wizard_folio_changes__dates_incongruence +msgid "" +"Indicates that there are reservations with different checkin and/or checkout" +msgstr "Indica que hay reservas con checkin y/o checkout diferentes" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room__parent_id +msgid "Indicates that this room is a child of another room" +msgstr "Indica que esta habitación es hija de otra habitación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__out_service_description +#: model:ir.model.fields,help:pms.field_pms_reservation__out_service_description +msgid "Indicates the cause of out of service" +msgstr "Causa de estar Fuera de Servicio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__default_invoice_to +#: model:ir.model.fields,help:pms.field_pms_reservation_line__default_invoice_to +#: model:ir.model.fields,help:pms.field_pms_service__default_invoice_to +#: model:ir.model.fields,help:pms.field_pms_service_line__default_invoice_to +msgid "" +"Indicates the contact to which this line will be\n" +" billed by default, if it is not established,\n" +" a guest or the generic contact will be used instead" +msgstr "" +"Indica el contacto al que se facturará por defecto esta línea\n" +" facturada por defecto, si no está establecida,\n" +" se utilizará en su lugar un invitado o el contacto genérico" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__ready_for_checkin +msgid "Indicates the reservations with checkin_partner data enought to checkin" +msgstr "" +"Indica las reservas con datos suficientes de los clientes para realizar el " +"checkin" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__consumed_on +#: model:ir.model.fields,help:pms.field_product_product__consumed_on +#: model:ir.model.fields,help:pms.field_product_template__consumed_on +msgid "Indicates when the product is consumed" +msgstr "Precio cuando el producto es vendido a los clientes" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_sale_channel__channel_type__indirect +msgid "Indirect" +msgstr "Indirecto" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__industry_id +msgid "Industry" +msgstr "Industria" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_amenity_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_form +msgid "Information" +msgstr "Información" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_ir_pms_property__value_integer +msgid "Integer Field Value" +msgstr "Valor de Campo Entero" + +#. module: pms +#: code:addons/pms/models/pms_board_service.py:0 +#, python-format +msgid "" +"Integrity error: There's multiple board services with the same code %s and " +"properties" +msgstr "" +"***Error de Integridad: Hay múltiples tipos de habitación con el código %s " +"con el mismo código y propiedades" + +#. module: pms +#: code:addons/pms/models/pms_room_type.py:0 +#: code:addons/pms/models/pms_room_type_class.py:0 +#, python-format +msgid "" +"Integrity error: There's multiple room types with the same code %s and " +"properties" +msgstr "" +"***Error de Integridad: Hay múltiples tipos de habitación con el código %s " +"con el mismo código y propiedades" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__internal_comment +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__internal_comment +#: model:ir.model.fields,field_description:pms.field_pms_folio__internal_comment +#: model:ir.model.fields,field_description:pms.field_pms_reservation__folio_internal_comment +msgid "Internal Folio Notes" +msgstr "Notas Internas del Folio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__internal_comment +#: model:ir.model.fields,help:pms.field_pms_booking_engine__internal_comment +#: model:ir.model.fields,help:pms.field_pms_folio__internal_comment +msgid "Internal Folio notes for Staff" +msgstr "Notas Internas del Folio para staff" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__partner_internal_comment +#: model:ir.model.fields,field_description:pms.field_pms_reservation__partner_internal_comment +msgid "Internal Partner Notes" +msgstr "Notas Internas del Cliente" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_amenity__default_code +msgid "Internal Reference" +msgstr "Referencias Internas" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.booking_duplicate +#: model_terms:ir.ui.view,arch_db:pms.booking_engine +msgid "Internal comment Folio" +msgstr "Comentario interno de la ficha" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__folio_internal_comment +msgid "Internal comment for folio" +msgstr "Comentario interno para la ficha" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__check_adults +msgid "Internal field to force room capacity validations" +msgstr "Campo interno para forzar validaciones de capacidad de habitaciones" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__partner_internal_comment +#: model:ir.model.fields,help:pms.field_pms_reservation__partner_internal_comment +msgid "Internal notes of the partner" +msgstr "Referencias Internas" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_amenity__default_code +msgid "Internal unique identifier of the amenity" +msgstr "Identificador único interno del servicio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__barcode +msgid "International Article Number used for product identification." +msgstr "" +"Número de artículo internacional usado para la identificación del producto." + +#. module: pms +#: code:addons/pms/wizards/wizard_split_join_swap_reservation.py:0 +#, python-format +msgid "Invalid date for reservation line " +msgstr "Fecha de linea de reserva duplicada" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Invalid domain operator %s for left of cancel" +msgstr "Operador del dominio no válido %s para left of checkin" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Invalid domain operator %s for left of checkin" +msgstr "Operador del dominio no válido %s para left of checkin" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Invalid domain operator %s for left of checkout" +msgstr "Operador del dominio no válido %s para left of checkin" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Invalid domain right operand %s for left of cancel" +msgstr "" +"El operador de la derecha del dominio no es válido %s para left of checkin" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Invalid domain right operand %s for left of checkin" +msgstr "" +"El operador de la derecha del dominio no es válido %s para left of checkin" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Invalid domain right operand %s for left of checkout" +msgstr "" +"El operador de la derecha del dominio no es válido %s para left of checkin" + +#. module: pms +#: code:addons/pms/wizards/wizard_split_join_swap_reservation.py:0 +#, python-format +msgid "Invalid reservation" +msgstr "Cancelar Reserva" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "Invalid token found! Token acquirer %s != %s" +msgstr "Se ha encontrado un token no válido. Adquirente de token %s != %s" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "Invalid token found! Token partner %s != %s" +msgstr "¡Ficha inválida encontrada! Ficha socio %s != %s" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__property_stock_inventory +msgid "Inventory Location" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__valuation +msgid "Inventory Valuation" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folio_invitations +msgid "Invitation email sent" +msgstr "Envío del correo electrónico de invitación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__invoice_warn +#: model_terms:ir.ui.view,arch_db:pms.report_invoice_document +msgid "Invoice" +msgstr "Factura" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__type +msgid "" +"Invoice & Delivery addresses are used in sales orders. Private addresses are " +"only visible by authorized users." +msgstr "" +"Las direcciones de facturación y entrega se usan en los pedidos de venta. " +"Las direcciones privadas solo son visibles para los usuarios autorizados." + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__invoice_to_agency +#: model:ir.model.fields,field_description:pms.field_pms_property__invoice_to_agency +#: model:ir.model.fields,field_description:pms.field_res_partner__invoice_to_agency +#: model:ir.model.fields,field_description:pms.field_res_users__invoice_to_agency +msgid "Invoice Agency" +msgstr "Facturar a la agencia" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__invoice_count +msgid "Invoice Count" +msgstr "Recuento de facturas" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.view_folio_advance_payment_inv +msgid "Invoice Folio Order" +msgstr "Facturación del Pedido del Folio" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__invoice_lines +msgid "Invoice Lines" +msgstr "Líneas de Facturación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__invoice_status +#: model:ir.model.fields,field_description:pms.field_pms_folio__invoice_status +#: model:ir.model.fields,field_description:pms.field_pms_reservation__invoice_status +#: model:ir.model.fields,field_description:pms.field_pms_service__invoice_status +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +msgid "Invoice Status" +msgstr "Estado de Facturación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__invoice_status +msgid "Invoice Status; it can be: invoiced, to invoice, no" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__invoice_status +msgid "Invoice Status; it can be: invoiced, to invoice, to confirm, no" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__partner_invoice_ids +msgid "Invoice address for current group." +msgstr "Dirección de facturación para el grupo actual." + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_advance_payment_inv__partner_invoice_id +msgid "Invoice address for current partner" +msgstr "Dirección de facturación para el grupo actual." + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__default_invoice_to +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__default_invoice_to +#: model:ir.model.fields,field_description:pms.field_pms_service__default_invoice_to +#: model:ir.model.fields,field_description:pms.field_pms_service_line__default_invoice_to +msgid "Invoice to" +msgstr "Factura para" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Invoiced" +msgstr "Facturado" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__qty_invoiced +msgid "Invoiced Quantity" +msgstr "Cantidad Facturada" + +#. module: pms +#: code:addons/pms/models/folio_sale_line.py:0 +#, python-format +msgid "Invoiced Quantity: %s" +msgstr "Cantidad Facturada: %s" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__move_ids +#: model:ir.model.fields,field_description:pms.field_pms_property__invoice_ids +#: model:ir.ui.menu,name:pms.pms_invoice_menu +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Invoices" +msgstr "Facturas" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.view_folio_advance_payment_inv +msgid "" +"Invoices will be created in draft so that you can review\n" +" them before validation." +msgstr "" +"Las facturas será creadas en borrador por lo que puede revisarlas\n" +"\t\tantes de validar." + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Invoicing" +msgstr "Facturación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__invoicing_month_day +#: model:ir.model.fields,field_description:pms.field_res_partner__invoicing_month_day +#: model:ir.model.fields,field_description:pms.field_res_users__invoicing_month_day +msgid "Invoicing Month Day" +msgstr "Mes Día de facturación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__invoicing_policy +#: model:ir.model.fields,field_description:pms.field_pms_room_type__invoice_policy +#: model:ir.model.fields,field_description:pms.field_res_partner__invoicing_policy +#: model:ir.model.fields,field_description:pms.field_res_users__invoicing_policy +msgid "Invoicing Policy" +msgstr "Política de Facturación" + +#. module: pms +#: model:ir.model,name:pms.model_ir_pms_property +msgid "IrPmsProperty" +msgstr "Propiedad IrPms" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__is_agency +#: model:ir.model.fields,field_description:pms.field_res_partner__is_agency +#: model:ir.model.fields,field_description:pms.field_res_users__is_agency +msgid "Is Agency" +msgstr "Es Agencia" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_service__is_board_service +#: model:ir.model.fields,field_description:pms.field_pms_service_line__is_board_service +msgid "Is Board Service" +msgstr "Es Regimen" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_service__is_cancel_penalty +msgid "Is Cancel Penalty" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__message_is_follower +#: model:ir.model.fields,field_description:pms.field_pms_folio__message_is_follower +#: model:ir.model.fields,field_description:pms.field_pms_property__message_is_follower +#: model:ir.model.fields,field_description:pms.field_pms_reservation__message_is_follower +#: model:ir.model.fields,field_description:pms.field_pms_room_type__message_is_follower +msgid "Is Follower" +msgstr "Es Seguidor" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__overbooking +msgid "Is Overbooking" +msgstr "Es Overbooking" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__is_product_variant +msgid "Is Product Variant" +msgstr "Es Variante de Producto" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__is_company +msgid "Is a Company" +msgstr "Es una compañía" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room__is_shared_room +msgid "Is a Shared Room" +msgstr "Es una habitación compartida" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__is_crib +#: model:ir.model.fields,field_description:pms.field_product_product__is_crib +#: model:ir.model.fields,field_description:pms.field_product_template__is_crib +msgid "Is a baby crib" +msgstr "Es una cuna de bebé" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__has_configurable_attributes +msgid "Is a configurable product" +msgstr "Es un producto configurable" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__is_downpayment +msgid "Is a down payment" +msgstr "Es un pago adelantado" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__is_pms_available +#: model:ir.model.fields,field_description:pms.field_product_product__is_pms_available +#: model:ir.model.fields,field_description:pms.field_product_template__is_pms_available +msgid "Is available in PMS" +msgstr "Está disponible en PMS" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__is_extra_bed +#: model:ir.model.fields,field_description:pms.field_product_product__is_extra_bed +#: model:ir.model.fields,field_description:pms.field_product_template__is_extra_bed +msgid "Is extra bed" +msgstr "Es una cama supletoria" + +#. module: pms +#: code:addons/pms/models/pms_checkin_partner.py:0 +#, python-format +msgid "Is mandatory indicate the reservation on the checkin" +msgstr "Es obligatorio indicar la reserva en el checkin" + +#. module: pms +#: code:addons/pms/models/pms_checkin_partner.py:0 +#, python-format +msgid "Is not possible to create the proposed check-in in this reservation" +msgstr "No es posible crear el check in en esta reserva" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_cancelation_rule__days_late +msgid "" +"Is number of days late in the cancelation rule if the value of the " +"apply_on_late field is specify days." +msgstr "" +"Es el número de días de retraso en la regla de cancelación si el valor del " +"campo apply_on_late es días specify days." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_cancelation_rule__days_noshow +msgid "" +"Is number of days no show in the cancelation rule if the value of the " +"apply_on_show field is specify days." +msgstr "" +"Es el número de días de ausencia en la regla de cancelación si el valor del " +"campo apply_on_show es specify days." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_wizard_reservation_lines_split__allowed_room_ids +msgid "It contains all available rooms for this line" +msgstr "Contiene todas las habitaciones disponibles para esta línea" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__allowed_room_ids +#: model:ir.model.fields,help:pms.field_pms_reservation_duplicate__allowed_room_ids +msgid "It contains all available rooms for this reservation" +msgstr "Ningún checkin fue realizado en esta reserva" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__to_assign +msgid "" +"It is True if the room of the reservation has been assigned automatically, " +"False if it was confirmed by a person in charge" +msgstr "" +"Es True si la habitación de la reserva ha sido asignada automáticamente, " +"False si ha sido confirmada por un responsable" + +#. module: pms +#: code:addons/pms/models/folio_sale_line.py:0 +#, python-format +msgid "" +"It is forbidden to modify the following fields\n" +" in a locked folio (fields already invoiced):\n" +"%s" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_checkin_partner.py:0 +#, python-format +msgid "It is not yet checkin day!" +msgstr "¡Aún no es el día del checkin!" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__qty_invoiced +msgid "It is the amount invoiced when an invoice is issued" +msgstr "Es el importe facturado cuando se emite una factura." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__checkin +msgid "It is the checkin date of the reservation, " +msgstr "Ningún checkin fue realizado en esta reserva" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__checkout +msgid "It is the checkout date of the reservation, " +msgstr "Checkout automático para reservas ya pasadas" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_line__reservation_id +msgid "It is the reservation in a reservation line" +msgstr "Es la reserva en una línea de reserva." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__preferred_room_id +msgid "" +"It's the preferred room assigned to reservation, empty if reservation is " +"splitted" +msgstr "" +"Es la habitación asignada a la reserva, vacía si la reserva está dividida" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_product_pricelist__item_ids +msgid "Items" +msgstr "Items" + +#. module: pms +#: model:ir.model.fields,help:pms.field_product_pricelist__item_ids +msgid "Items for which the pricelist is made up" +msgstr "Items para los que se compone la tarifa" + +#. module: pms +#: code:addons/pms/models/pms_checkin_partner.py:0 +#, python-format +msgid "Its too late to checkin" +msgstr "Es demasiado tarde para hacer el checkin/registrar" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__function +msgid "Job Position" +msgstr "Puesto de trabajo" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation_split_join_swap_wizard__operation__join +#: model_terms:ir.ui.view,arch_db:pms.reservation_wizard +msgid "Join reservation" +msgstr "Desde la reserva" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Join the reservation" +msgstr "Unificar la reserva" + +#. module: pms +#: model:ir.model,name:pms.model_account_journal +#: model:ir.model.fields,field_description:pms.field_account_bank_statement__journal_id +msgid "Journal" +msgstr "Diario" + +#. module: pms +#: model:ir.model,name:pms.model_account_move +#: model:ir.model.fields,field_description:pms.field_account_move_line__move_id +msgid "Journal Entry" +msgstr "Asiento contable" + +#. module: pms +#: model:ir.model,name:pms.model_account_move_line +msgid "Journal Item" +msgstr "Apunte contable" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__journal_item_count +msgid "Journal Items" +msgstr "Artículos Diarios" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__journal_normal_invoice_id +msgid "Journal used to create the normal invoice" +msgstr "Diario utilizado para crear la factura normal" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__journal_simplified_invoice_id +msgid "Journal used to create the simplified invoice" +msgstr "Diario utilizado para crear la factura simplificada" + +#. module: pms +#: model:pms.room.type,name:pms.demo_pms_room_type_junior_suite +#: model:product.product,name:pms.demo_pms_room_type_junior_suite_product_product +msgid "Junior suite" +msgstr "Habitación Junior" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__property_account_income_id +msgid "" +"Keep this field empty to use the default value from the product category." +msgstr "" +"Mantener este campo vacío para usar el valor por defecto de la categoría del " +"producto." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__property_account_expense_id +msgid "" +"Keep this field empty to use the default value from the product category. If " +"anglo-saxon accounting with automated valuation method is configured, the " +"expense account on the product category will be used." +msgstr "" +"Matener este campo vació para usar el valor por defecto de la categoría del " +"producto. Si la contabilidad anglosajona con un método de evaluación " +"automático está configurado, la cuenta de gastos de la categoría del " +"producto será usada." + +#. module: pms +#: model:pms.amenity.type,name:pms.pms_amenity_type_kitchen_facilities +msgid "Kitchen facilities" +msgstr "Instalaciones de la cocina" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_account_move_line__name +msgid "Label" +msgstr "Descripción" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__lang +#: model:ir.model.fields,field_description:pms.field_pms_property__lang +#: model:ir.model.fields,field_description:pms.field_pms_reservation__lang +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Language" +msgstr "Lenguaje" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__lang +msgid "Language used for the folio" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__last_checkout +msgid "Last Folio Checkout" +msgstr "Checkout del folio" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_account_analytic_distribution____last_update +#: model:ir.model.fields,field_description:pms.field_account_analytic_line____last_update +#: model:ir.model.fields,field_description:pms.field_account_bank_statement____last_update +#: model:ir.model.fields,field_description:pms.field_account_bank_statement_line____last_update +#: model:ir.model.fields,field_description:pms.field_account_journal____last_update +#: model:ir.model.fields,field_description:pms.field_account_move____last_update +#: model:ir.model.fields,field_description:pms.field_account_move_line____last_update +#: model:ir.model.fields,field_description:pms.field_account_payment____last_update +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv____last_update +#: model:ir.model.fields,field_description:pms.field_folio_sale_line____last_update +#: model:ir.model.fields,field_description:pms.field_ir_config_parameter____last_update +#: model:ir.model.fields,field_description:pms.field_ir_http____last_update +#: model:ir.model.fields,field_description:pms.field_ir_pms_property____last_update +#: model:ir.model.fields,field_description:pms.field_mail_compose_message____last_update +#: model:ir.model.fields,field_description:pms.field_payment_acquirer____last_update +#: model:ir.model.fields,field_description:pms.field_payment_transaction____last_update +#: model:ir.model.fields,field_description:pms.field_pms_advanced_filters_wizard____last_update +#: model:ir.model.fields,field_description:pms.field_pms_amenity____last_update +#: model:ir.model.fields,field_description:pms.field_pms_amenity_type____last_update +#: model:ir.model.fields,field_description:pms.field_pms_automated_mails____last_update +#: model:ir.model.fields,field_description:pms.field_pms_availability____last_update +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan____last_update +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule____last_update +#: model:ir.model.fields,field_description:pms.field_pms_board_service____last_update +#: model:ir.model.fields,field_description:pms.field_pms_board_service_line____last_update +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type____last_update +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type_line____last_update +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate____last_update +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine____last_update +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule____last_update +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner____last_update +#: model:ir.model.fields,field_description:pms.field_pms_folio____last_update +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard____last_update +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard____last_update +#: model:ir.model.fields,field_description:pms.field_pms_num_rooms_selection____last_update +#: model:ir.model.fields,field_description:pms.field_pms_property____last_update +#: model:ir.model.fields,field_description:pms.field_pms_reservation____last_update +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate____last_update +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line____last_update +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard____last_update +#: model:ir.model.fields,field_description:pms.field_pms_room____last_update +#: model:ir.model.fields,field_description:pms.field_pms_room_type____last_update +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class____last_update +#: model:ir.model.fields,field_description:pms.field_pms_sale_channel____last_update +#: model:ir.model.fields,field_description:pms.field_pms_service____last_update +#: model:ir.model.fields,field_description:pms.field_pms_service_line____last_update +#: model:ir.model.fields,field_description:pms.field_pms_several_partners_wizard____last_update +#: model:ir.model.fields,field_description:pms.field_pms_team_member____last_update +#: model:ir.model.fields,field_description:pms.field_pms_ubication____last_update +#: model:ir.model.fields,field_description:pms.field_pms_wizard_reservation_lines_split____last_update +#: model:ir.model.fields,field_description:pms.field_product_pricelist____last_update +#: model:ir.model.fields,field_description:pms.field_product_pricelist_item____last_update +#: model:ir.model.fields,field_description:pms.field_product_product____last_update +#: model:ir.model.fields,field_description:pms.field_product_template____last_update +#: model:ir.model.fields,field_description:pms.field_res_company____last_update +#: model:ir.model.fields,field_description:pms.field_res_country____last_update +#: model:ir.model.fields,field_description:pms.field_res_partner____last_update +#: model:ir.model.fields,field_description:pms.field_res_partner_category____last_update +#: model:ir.model.fields,field_description:pms.field_res_partner_id_category____last_update +#: model:ir.model.fields,field_description:pms.field_res_partner_id_number____last_update +#: model:ir.model.fields,field_description:pms.field_res_users____last_update +#: model:ir.model.fields,field_description:pms.field_room_closure_reason____last_update +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes____last_update +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio____last_update +msgid "Last Modified on" +msgstr "Última modificación en" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__lastname +msgid "Last Name" +msgstr "Apellido" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__write_uid +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__write_uid +#: model:ir.model.fields,field_description:pms.field_ir_pms_property__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_advanced_filters_wizard__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_amenity__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_amenity_type__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_automated_mails__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_availability__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_board_service__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_board_service_line__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type_line__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_folio__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_num_rooms_selection__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_property__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_reservation__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_room__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_room_type__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_sale_channel__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_service__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_service_line__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_several_partners_wizard__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_team_member__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_ubication__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_wizard_reservation_lines_split__write_uid +#: model:ir.model.fields,field_description:pms.field_room_closure_reason__write_uid +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__write_uid +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__write_uid +msgid "Last Updated by" +msgstr "Última Actualización por" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__write_date +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__write_date +#: model:ir.model.fields,field_description:pms.field_ir_pms_property__write_date +#: model:ir.model.fields,field_description:pms.field_pms_advanced_filters_wizard__write_date +#: model:ir.model.fields,field_description:pms.field_pms_amenity__write_date +#: model:ir.model.fields,field_description:pms.field_pms_amenity_type__write_date +#: model:ir.model.fields,field_description:pms.field_pms_automated_mails__write_date +#: model:ir.model.fields,field_description:pms.field_pms_availability__write_date +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan__write_date +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__write_date +#: model:ir.model.fields,field_description:pms.field_pms_board_service__write_date +#: model:ir.model.fields,field_description:pms.field_pms_board_service_line__write_date +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type_line__write_date +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__write_date +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__write_date +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__write_date +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__write_date +#: model:ir.model.fields,field_description:pms.field_pms_folio__write_date +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__write_date +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__write_date +#: model:ir.model.fields,field_description:pms.field_pms_num_rooms_selection__write_date +#: model:ir.model.fields,field_description:pms.field_pms_property__write_date +#: model:ir.model.fields,field_description:pms.field_pms_reservation__write_date +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__write_date +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__write_date +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__write_date +#: model:ir.model.fields,field_description:pms.field_pms_room__write_date +#: model:ir.model.fields,field_description:pms.field_pms_room_type__write_date +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class__write_date +#: model:ir.model.fields,field_description:pms.field_pms_sale_channel__write_date +#: model:ir.model.fields,field_description:pms.field_pms_service__write_date +#: model:ir.model.fields,field_description:pms.field_pms_service_line__write_date +#: model:ir.model.fields,field_description:pms.field_pms_several_partners_wizard__write_date +#: model:ir.model.fields,field_description:pms.field_pms_team_member__write_date +#: model:ir.model.fields,field_description:pms.field_pms_ubication__write_date +#: model:ir.model.fields,field_description:pms.field_pms_wizard_reservation_lines_split__write_date +#: model:ir.model.fields,field_description:pms.field_room_closure_reason__write_date +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__write_date +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__write_date +msgid "Last Updated on" +msgstr "Última Actualización en" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__lastname +#: model:ir.model.fields,field_description:pms.field_res_partner__lastname +#: model:ir.model.fields,field_description:pms.field_res_users__lastname +msgid "Last name" +msgstr "Apellido" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__last_time_entries_checked +msgid "" +"Last time the invoices & payments matching was performed for this partner. " +"It is set either if there's not at least an unreconciled debit and an " +"unreconciled credit or if you click the \"Done\" button." +msgstr "" +"Última vez que se realizó la igualación de las facturas y pagos para este " +"cliente. Se establece si no hay al menos un débito no conciliado y un " +"crédito no conciliado o si hace click en el botón \"Listo\"." + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Lastname *" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "Lastnames" +msgstr "Apellidos" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__cancelled_reason__late +#: model_terms:ir.ui.view,arch_db:pms.pms_cancelation_rule_form +msgid "Late" +msgstr "Tarde" + +#. module: pms +#: model:product.product,name:pms.pms_service_late_checkout +#: model:product.template,name:pms.pms_service_late_checkout_product_template +msgid "Late Check-out" +msgstr "Late Check out" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Late Payment" +msgstr "Pago por cancelación fuera de plazo" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__apply_on_late +msgid "Late apply on" +msgstr "Aplicar penalización" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__days_late +msgid "Late first days" +msgstr "Primeros días de la penalización" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Lates and NoShows" +msgstr "Cancelación fuera de plazo y No Shows" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__last_time_entries_checked +msgid "Latest Invoices & Payments Matching Date" +msgstr "Últimas facturas y Pagos por fecha de conciliación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__recompute_prices +msgid "" +"Leave unchecked if you want to respect\n" +" the price of the original reservation regardless\n" +" of what is marked in the rate" +msgstr "" +"Dejar sin marcar si desea respetar\n" +" el precio de la reserva original independientemente\n" +" de lo marcado en la tarifa" + +#. module: pms +#: model:ir.model,name:pms.model_pms_wizard_reservation_lines_split +msgid "Lines available to split" +msgstr "Líneas disponibles para dividir" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__adults +msgid "List of adults there in guest list" +msgstr "Lista de adultos en el rooming" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room__room_amenity_ids +msgid "List of amenities included in room" +msgstr "Lista de características incluidas en la habitación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__room_amenity_ids +msgid "List of amenities included in room type" +msgstr "Lista de características incluidas en el tipo de habitación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__location_id +msgid "Location" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__state__done +msgid "Locked" +msgstr "Bloqueado" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_sale_channel__icon +msgid "Logo" +msgstr "" + +#. module: pms +#: model:product.product,name:pms.pms_service_lunch +#: model:product.template,name:pms.pms_service_lunch_product_template +msgid "Lunch" +msgstr "Comida" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__mail_information +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Mail Information" +msgstr "Información postal" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__privacy_policy +msgid "Mail privacy policy " +msgstr "Política de privacidad del correo " + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__message_main_attachment_id +#: model:ir.model.fields,field_description:pms.field_pms_folio__message_main_attachment_id +#: model:ir.model.fields,field_description:pms.field_pms_property__message_main_attachment_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__message_main_attachment_id +#: model:ir.model.fields,field_description:pms.field_pms_room_type__message_main_attachment_id +msgid "Main Attachment" +msgstr "Adjunto Principal" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__manager_user_id +msgid "Main Manager" +msgstr "Director General" + +#. module: pms +#: model:room.closure.reason,name:pms.pms_room_closure_reason_maintenance +msgid "Maintenance" +msgstr "Mantenimiento" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_checkin_partner__gender__male +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Male" +msgstr "Hombre" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folio_invitations +msgid "Mandatory Firstname" +msgstr "Obligatorio Nombre" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_property__default_invoicing_policy__manual +#: model:ir.model.fields.selection,name:pms.selection__res_company__pms_invoice_downpayment_policy__no +#: model:ir.model.fields.selection,name:pms.selection__res_partner__invoice_to_agency__manual +#: model:ir.model.fields.selection,name:pms.selection__res_partner__invoicing_policy__manual +msgid "Manual" +msgstr "Manual" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__valuation +msgid "" +"Manual: The accounting entries to value the inventory are not posted " +"automatically.\n" +" Automated: An accounting entry is automatically created to value the " +"inventory when a product enters or leaves the company.\n" +" " +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__service_type +msgid "" +"Manually set quantities on order: Invoice based on the manually entered " +"quantity, without creating an analytic account.\n" +"Timesheets on contract: Invoice based on the tracked hours on the related " +"timesheet.\n" +"Create a task and track hours: Create a task on the sales order validation " +"and track the work hours." +msgstr "" +"Las cantidades fijadas manualmente en el pedido: factura basada en la " +"cantidad ingresada manualmente, sin crear una cuenta analítica.\n" +"La plantilla horaria del contrato: factura basada en las horas registradas " +"en la plantilla horaria relacionada..\n" +"Cree una tarea y realice un seguimiento de las horas: cree una tarea en la " +"validación de la orden de venta y realice un seguimiento de las horas de " +"trabajo." + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__margin_days_autoinvoice +msgid "Margin Days" +msgstr "Días de Margen" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__prepaid_warning_days +msgid "" +"Margin in days to create a notice if a payment advance has " +"not been recorded" +msgstr "" +"El margen en días para crear un aviso si el pago avanzado no fue registrado" + +#. module: pms +#: model:ir.ui.menu,name:pms.menu_pms_room_massive_changes_wizard +msgid "Massive Changes" +msgstr "Cambios Masivos" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.availability_view_form +#: model_terms:ir.ui.view,arch_db:pms.product_pricelist_view_form +msgid "Massive changes" +msgstr "Cambios Masivos" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_wizard_massive_changes +msgid "Massive changes on Pricelist & Availability Plans" +msgstr "Cambios masivos en las tarifas y planes de disponibilidad" + +#. module: pms +#: code:addons/pms/wizards/wizard_massive_changes.py:0 +#, python-format +msgid "Massive changes on Pricelist and Availability Plans" +msgstr "Cambios masivos en la Lista de Precios y los Planes de Disponibilidad" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__max_amount_simplified_invoice +msgid "Max Amount Simplified Invoice" +msgstr "Importe Máximo Factura Simplificada" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__max_reservation_priority +#: model:ir.model.fields,help:pms.field_pms_folio__max_reservation_priority +msgid "Max reservation priority on the entire folio" +msgstr "Máxima prioridad de reserva en toda la ficha" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__max_avail +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__max_avail +msgid "Max. Availability" +msgstr "Disponibilidad Máx" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__max_stay +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__max_stay +msgid "Max. Stay" +msgstr "Estancia Máx" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__max_stay_arrival +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__max_stay_arrival +msgid "Max. Stay Arrival" +msgstr "Máx. Estancia Llegada" + +#. module: pms +#: code:addons/pms/models/pms_availability_plan_rule.py:0 +#, python-format +msgid "Max. Stay Arrival can't be less than Min. Stay Arrival" +msgstr "Máx. Estancia Llegada no puede ser menor que la Min. Estancia Llegada" + +#. module: pms +#: code:addons/pms/models/pms_availability_plan_rule.py:0 +#, python-format +msgid "Max. Stay Arrival can't be less than zero" +msgstr "Máx. Estancia Llegada no puede ser menor que cero" + +#. module: pms +#: code:addons/pms/models/pms_availability_plan_rule.py:0 +#, python-format +msgid "Max. Stay can't be less than Min. Stay" +msgstr "La Estancia Máxima no puede ser menor que la Estancia Mínima" + +#. module: pms +#: code:addons/pms/models/pms_availability_plan_rule.py:0 +#, python-format +msgid "Max. Stay can't be less than zero" +msgstr "La Estancia Máxima no puede ser menor que cero" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_cancelation_rule_form +msgid "Max. days InTime before Checkin" +msgstr "Máximo de días a tiempo antes del Checkin" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__max_amount_simplified_invoice +msgid "Maximum amount to create the simplified invoice" +msgstr "Importe máximo para crear la factura simplificada" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_cancelation_rule__days_intime +msgid "Maximum number of days for free cancellation before Checkin" +msgstr "" +"Número máximo de días para que la cancelación sea gratis antes del checkin" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan_rule__max_avail +msgid "Maximum simultaneous availability on own Booking Engine" +msgstr "Disponibilidad Simultánea Máxima en el propio Motor de Reservas." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__default_max_avail +msgid "" +"Maximum simultaneous availability on own Booking Engine given no " +"availability rules. Use `-1` for using maximum simultaneous availability." +msgstr "" +"La disponibilidad simultánea máxima en el propio Motor de Reservas sin " +"Reglas de Disponibilidad. Utilice '-1' para usar la disponibilidad máxima " +"simultánea." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_massive_changes_wizard__max_avail +msgid "Maximum simultaneous availability on own Booking Engine." +msgstr "Disponibilidad Simultánea Máxima el el propio Motor de Reservas." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan_rule__max_stay +msgid "Maximum stay" +msgstr "Estancia máxima" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan_rule__max_stay_arrival +msgid "Maximum stay if checkin is today" +msgstr "Estancia máxima si el check in es hoy" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Member Team" +msgstr "Miembro del Equipo" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__message_has_error +#: model:ir.model.fields,field_description:pms.field_pms_folio__message_has_error +#: model:ir.model.fields,field_description:pms.field_pms_property__message_has_error +#: model:ir.model.fields,field_description:pms.field_pms_reservation__message_has_error +#: model:ir.model.fields,field_description:pms.field_pms_room_type__message_has_error +msgid "Message Delivery error" +msgstr "Error en la entrega del mensaje" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__invoice_warn_msg +msgid "Message for Invoice" +msgstr "Mensaje para facturar" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__sale_warn_msg +msgid "Message for Sales Order" +msgstr "Mensaje para pedido de ventas" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__sale_line_warn_msg +msgid "Message for Sales Order Line" +msgstr "Mensaje para línea de pedido de ventas" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__picking_warn_msg +msgid "Message for Stock Picking" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__message_ids +#: model:ir.model.fields,field_description:pms.field_pms_folio__message_ids +#: model:ir.model.fields,field_description:pms.field_pms_property__message_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__message_ids +#: model:ir.model.fields,field_description:pms.field_pms_room_type__message_ids +msgid "Messages" +msgstr "Mensajes" + +#. module: pms +#: model:pms.amenity,name:pms.pms_amenity_microwave_oven +msgid "Microwave oven" +msgstr "Horno microondas" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__min_price +msgid "Min. Price" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__min_quantity +msgid "Min. Quantity" +msgstr "Cantidad Mín" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__min_stay +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__min_stay +msgid "Min. Stay" +msgstr "Estancia mínima" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__min_stay_arrival +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__min_stay_arrival +msgid "Min. Stay Arrival" +msgstr "Min. Estancia Llegada" + +#. module: pms +#: code:addons/pms/models/pms_availability_plan_rule.py:0 +#, python-format +msgid "Min. Stay Arrival can't be less than zero" +msgstr "Min. Estancia Llegada no puede ser menor que cero" + +#. module: pms +#: code:addons/pms/models/pms_availability_plan_rule.py:0 +#, python-format +msgid "Min. Stay can't be less than zero" +msgstr "La estancia mínima no puede ser menor que cero" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__orderpoint_ids +msgid "Minimum Stock Rules" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__min_price +msgid "Minimum price for a room type" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan_rule__min_stay +msgid "Minimum stay" +msgstr "Estancia mínima" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan_rule__min_stay_arrival +msgid "Minimum stay if checkin is today" +msgstr "Estancia mínima si el check in es hoy" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__mobile +#: model:ir.model.fields,field_description:pms.field_pms_folio__mobile +#: model:ir.model.fields,field_description:pms.field_pms_property__mobile +#: model:ir.model.fields,field_description:pms.field_pms_reservation__mobile +#: model:ir.model.fields,field_description:pms.field_res_partner__mobile +#: model:ir.model.fields,field_description:pms.field_res_users__mobile +msgid "Mobile" +msgstr "Móvil" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Mobile (Optional)" +msgstr "Móvil (Opcional)" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_ir_pms_property__model_id +#: model_terms:ir.ui.view,arch_db:pms.advanced_filters_wizard +msgid "Model" +msgstr "Modelo" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__property_modified_template +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Modification Email" +msgstr "Modificación Correo electrónico" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__modification_type +msgid "Modification Type" +msgstr "Tipo de Modificación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__property_modified_template +msgid "Modification email template" +msgstr "Plantilla de modificación de correo electrónico" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__cancelled_reason__modified +msgid "Modified" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.wizard_folio_changes_view_form +msgid "Monday" +msgstr "Lunes" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_property__default_invoicing_policy__month_day +#: model:ir.model.fields.selection,name:pms.selection__res_partner__invoicing_policy__month_day +msgid "Month Day Invoice" +msgstr "Mes Día Factura" + +#. module: pms +#: code:addons/pms/models/product_product.py:0 +#, python-format +msgid "More than one room found for the same product" +msgstr "Se ha encontrado más de una sala para el mismo producto" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.res_users_view_form +msgid "Multi Properties" +msgstr "Multi Hoteles" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__my_activity_date_deadline +#: model:ir.model.fields,field_description:pms.field_pms_folio__my_activity_date_deadline +#: model:ir.model.fields,field_description:pms.field_pms_property__my_activity_date_deadline +#: model:ir.model.fields,field_description:pms.field_pms_reservation__my_activity_date_deadline +#: model:ir.model.fields,field_description:pms.field_pms_room_type__my_activity_date_deadline +msgid "My Activity Deadline" +msgstr "Fecha Límite de Mi Actividad" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "My Reservations" +msgstr "Mis reservas" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_automated_mails__name +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__name +#: model:ir.model.fields,field_description:pms.field_pms_property__name +#: model:ir.model.fields,field_description:pms.field_pms_room_type__name +#: model:ir.model.fields,field_description:pms.field_pms_team_member__name +#: model:ir.model.fields,field_description:pms.field_room_closure_reason__name +#: model_terms:ir.ui.view,arch_db:pms.availability_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_form +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folio_invitations +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "Name" +msgstr "Nombre" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_form +msgid "Name in reports" +msgstr "Nombre en los Informes" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan__name +msgid "Name of availability plan" +msgstr "Nombre del plan de disponibilidad" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_automated_mails__name +msgid "Name of the automated mail." +msgstr "Nombre del correo automatizado." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type_class__name +msgid "Name of the room type class" +msgstr "Nombre de la clase del tipo de habitación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__partner_id +msgid "Name of who made the reservation" +msgstr "Ningún checkin fue realizado en esta reserva" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_account_move_line__name_changed_by_user +msgid "Name set manually" +msgstr "Nombre fijado manualmente" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Name *" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__nationality_id +#: model:ir.model.fields,field_description:pms.field_pms_property__nationality_id +#: model:ir.model.fields,field_description:pms.field_res_partner__nationality_id +#: model:ir.model.fields,field_description:pms.field_res_users__nationality_id +msgid "Nationality" +msgstr "Nacionalidad" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Nationality *" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__res_partner__invoice_to_agency__never +msgid "Never" +msgstr "Nunca" + +#. module: pms +#: code:addons/pms/models/pms_checkin_partner.py:0 +#: code:addons/pms/models/pms_folio.py:0 +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "New" +msgstr "Nuevo" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__new_board_service_id +msgid "New Board Service" +msgstr "Nuevo régimen" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__new_checkin +msgid "New Checkin" +msgstr "Nuevo Registro Entrada" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__new_checkout +msgid "New Checkout" +msgstr "Nuevo Registro de Salida" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__new_discount +msgid "New Discount %" +msgstr "Nuevo Descuento %" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__new_price +msgid "New Price" +msgstr "Nuevo Precio" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__new_service_id +msgid "New Service" +msgstr "Nuevo Servicio" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "Next 7 days" +msgstr "Próximos 7 días" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__activity_date_deadline +#: model:ir.model.fields,field_description:pms.field_pms_folio__activity_date_deadline +#: model:ir.model.fields,field_description:pms.field_pms_property__activity_date_deadline +#: model:ir.model.fields,field_description:pms.field_pms_reservation__activity_date_deadline +#: model:ir.model.fields,field_description:pms.field_pms_room_type__activity_date_deadline +msgid "Next Activity Deadline" +msgstr "Fecha Límite para la Próxima Actividad" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__activity_summary +#: model:ir.model.fields,field_description:pms.field_pms_folio__activity_summary +#: model:ir.model.fields,field_description:pms.field_pms_property__activity_summary +#: model:ir.model.fields,field_description:pms.field_pms_reservation__activity_summary +#: model:ir.model.fields,field_description:pms.field_pms_room_type__activity_summary +msgid "Next Activity Summary" +msgstr "Resumen de la Siguiente Actividad" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__activity_type_id +#: model:ir.model.fields,field_description:pms.field_pms_folio__activity_type_id +#: model:ir.model.fields,field_description:pms.field_pms_property__activity_type_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__activity_type_id +#: model:ir.model.fields,field_description:pms.field_pms_room_type__activity_type_id +msgid "Next Activity Type" +msgstr "Siguiente tipo de actividad" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__reservation_line_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__nights +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__nights +msgid "Nights" +msgstr "Noches" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Nights)" +msgstr "Noches)" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "No Checkins!" +msgstr "¡Sin Registros!" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__cancelled_reason__noshow +#: model_terms:ir.ui.view,arch_db:pms.pms_cancelation_rule_form +msgid "No Show" +msgstr "No Show" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__apply_on_noshow +msgid "No Show apply on" +msgstr "Aplicar No Show en" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "No checkin was made for this reservation" +msgstr "Ningún checkin fue realizado en esta reserva" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "No entry has been recorded in this reservation" +msgstr "No se ha registrado ninguna entrada en esta reserva" + +#. module: pms +#: code:addons/pms/models/account_move.py:0 +#, python-format +msgid "" +"No journal could be found in company %(company_name)s\n" +" for any of those types: %(journal_types)s" +msgstr "" +"No se ha encontrado ningún diario en la empresa %(company_name)s\n" +" para cualquiera de estos tipos: %(journal_types)s" + +#. module: pms +#: code:addons/pms/models/account_move.py:0 +#, python-format +msgid "" +"No journal could be found in property %(property_name)s\n" +" for any of those types: %(journal_types)s" +msgstr "" +"No se ha podido encontrar ningún diario en la propiedad %(property_name)s\n" +" para cualquiera de estos tipos: %(journal_types)s" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "No person from reserve %s has arrived" +msgstr "Ninguna persona de la reserva %s ha llegado" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__days_noshow +msgid "NoShow first days" +msgstr "NoShow los primeros días" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_booking_duplicate__reservation_type__normal +#: model:ir.model.fields.selection,name:pms.selection__pms_booking_engine__reservation_type__normal +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__reservation_type__normal +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__reservation_type__normal +msgid "Normal" +msgstr "Normal" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__journal_normal_invoice_id +msgid "Normal Invoice Journal" +msgstr "Diario de Facturas Normal" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__email_normalized +msgid "Normalized Email" +msgstr "Email normalizado" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__payment_state__not_paid +msgid "Not Paid" +msgstr "No pagado" + +#. module: pms +#: code:addons/pms/models/pms_property.py:0 +#, python-format +msgid "Not invoiced due to pending amounts and cancelled reservations" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__folio_sale_line__display_type__line_note +msgid "Note" +msgstr "Nota" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__comment +#: model:ir.model.fields,field_description:pms.field_res_partner__comment +#: model:ir.model.fields,field_description:pms.field_res_users__comment +msgid "Notes" +msgstr "Notas" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__folio_sale_line__invoice_status__no +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__invoice_status__no +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__invoice_status__no +#: model:ir.model.fields.selection,name:pms.selection__pms_service__invoice_status__no +msgid "Nothing to Invoice" +msgstr "Nada para Facturar" + +#. module: pms +#: code:addons/pms/wizards/folio_make_invoice_advance.py:0 +#, python-format +msgid "Nothing to invoice" +msgstr "Nada paraFacturar" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__payment_state__nothing_to_pay +msgid "Nothing to pay" +msgstr "Nada que pagar" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__cardex_warning +msgid "Notice under the signature on the traveler's ticket." +msgstr "Aviso bajo la firma del billete del viajero." + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__message_needaction_counter +#: model:ir.model.fields,field_description:pms.field_pms_folio__message_needaction_counter +#: model:ir.model.fields,field_description:pms.field_pms_property__message_needaction_counter +#: model:ir.model.fields,field_description:pms.field_pms_reservation__message_needaction_counter +#: model:ir.model.fields,field_description:pms.field_pms_room_type__message_needaction_counter +msgid "Number of Actions" +msgstr "Número de Acciones" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__number_of_cancelled_rooms +msgid "Number of Cancelled Rooms" +msgstr "Número de habitaciones canceladas" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__folios_count +#: model:ir.model.fields,field_description:pms.field_res_partner__folios_count +#: model:ir.model.fields,field_description:pms.field_res_users__folios_count +msgid "Number of Folios" +msgstr "Número de fichas" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__reservations_count +#: model:ir.model.fields,field_description:pms.field_res_partner__reservations_count +#: model:ir.model.fields,field_description:pms.field_res_users__reservations_count +msgid "Number of Reservations" +msgstr "Número de reservas" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__number_of_rooms +msgid "Number of Rooms" +msgstr "Número de Habitaciones" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__value_num_rooms_selected +msgid "Number of Rooms Selected" +msgstr "Número de Habitaciones Seleccionadas" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__number_of_services +msgid "Number of Services" +msgstr "Número de reglas de precio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__number_of_cancelled_rooms +msgid "Number of cancelled rooms in folio." +msgstr "Número de habitaciones canceladas en la ficha" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__checkin_partner_count +msgid "Number of checkin partners in a reservation" +msgstr "Ningún checkin fue realizado en esta reserva" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__checkin_partner_pending_count +msgid "Number of checkin partners pending to checkin in a reservation" +msgstr "Ningún checkin fue realizado en esta reserva" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__children_occupying +msgid "Number of children there in guest list whose presence counts" +msgstr "Numero de niños en el rooming que ocupan plaza" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__message_has_error_counter +#: model:ir.model.fields,field_description:pms.field_pms_folio__message_has_error_counter +#: model:ir.model.fields,field_description:pms.field_pms_property__message_has_error_counter +#: model:ir.model.fields,field_description:pms.field_pms_reservation__message_has_error_counter +#: model:ir.model.fields,field_description:pms.field_pms_room_type__message_has_error_counter +msgid "Number of errors" +msgstr "Número de errores" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room__extra_beds_allowed +msgid "Number of extra beds allowed in room" +msgstr "Camas Supletorias Permtidas" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__folios_count +#: model:ir.model.fields,help:pms.field_res_partner__folios_count +#: model:ir.model.fields,help:pms.field_res_users__folios_count +msgid "Number of folios of the partner" +msgstr "Número de fichas del cliente" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__count_pending_arrival +msgid "Number of guest with pending checkin" +msgstr "Número de mensajes que requiere una acción" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__message_needaction_counter +#: model:ir.model.fields,help:pms.field_pms_folio__message_needaction_counter +#: model:ir.model.fields,help:pms.field_pms_property__message_needaction_counter +#: model:ir.model.fields,help:pms.field_pms_reservation__message_needaction_counter +#: model:ir.model.fields,help:pms.field_pms_room_type__message_needaction_counter +msgid "Number of messages which requires an action" +msgstr "Número de mensajes que requiere una acción" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__message_has_error_counter +#: model:ir.model.fields,help:pms.field_pms_folio__message_has_error_counter +#: model:ir.model.fields,help:pms.field_pms_property__message_has_error_counter +#: model:ir.model.fields,help:pms.field_pms_reservation__message_has_error_counter +#: model:ir.model.fields,help:pms.field_pms_room_type__message_has_error_counter +msgid "Number of messages with delivery error" +msgstr "Número de mensajes con error de entrega" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__nights +msgid "Number of nights of a reservation" +msgstr "Número de noches de la reserva" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__pricelist_item_count +msgid "Number of price rules" +msgstr "Número de reglas de precio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__reservations_count +#: model:ir.model.fields,help:pms.field_res_partner__reservations_count +#: model:ir.model.fields,help:pms.field_res_users__reservations_count +msgid "Number of reservations of the partner" +msgstr "Número de reservas del cliente" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__availability +msgid "Number of rooms available" +msgstr "Número de habitaciones disponibles" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__number_of_rooms +msgid "Number of rooms in folio. Canceled rooms do not count." +msgstr "" +"Número de habitaciones en la ficha. Las habitaciones canceladas no cuentan." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio_availability_wizard__num_rooms_available +msgid "Number of rooms that are available" +msgstr "%s: Tipo de habitación no disponible" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__number_of_services +msgid "Number of services in the folio" +msgstr "Número de reglas de precio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__product_qty +msgid "Number of services that were sold" +msgstr "Número de reglas de precio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__message_unread_counter +#: model:ir.model.fields,help:pms.field_pms_folio__message_unread_counter +#: model:ir.model.fields,help:pms.field_pms_property__message_unread_counter +#: model:ir.model.fields,help:pms.field_pms_reservation__message_unread_counter +#: model:ir.model.fields,help:pms.field_pms_room_type__message_unread_counter +msgid "Number of unread messages" +msgstr "Número de mensajes no leídos" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__children +msgid "" +"Number total of children there in guest list,whose presence counts or not" +msgstr "Número total de niños en el roomins" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__occupied_room +msgid "Occupied Room" +msgstr "Habitación Ocupada" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__occupies_availability +msgid "Occupies" +msgstr "Ocupa" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__massive_changes_on +msgid "On" +msgstr "Sobre" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_checkin_partner__state__onboard +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__state__onboard +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "On Board" +msgstr "Dentro" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +msgid "On Board Tomorrow" +msgstr "Dentro Mañana" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_sale_channel__is_on_line +msgid "On Line" +msgstr "En línea" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Only Room" +msgstr "Solo habitaciones" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Only Services" +msgstr "Solo servicios" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__agency_id +msgid "Only allowed if the field of partner is_agency is True" +msgstr "Solo se permite si el campo del partner is_agency es True" + +#. module: pms +#: model:ir.model.constraint,message:pms.constraint_pms_availability_room_type_registry_unique +msgid "" +"Only can exists one availability in the same day for " +"the same room type!" +msgstr "" +"Solo puede existir una regla de disponibilidad el mismo día para el mismo " +"tipo de habitación!" + +#. module: pms +#: model:ir.model.constraint,message:pms.constraint_pms_availability_plan_rule_room_type_registry_unique +msgid "" +"Only can exists one availability rule in the same " +"day for the same room type!" +msgstr "" +"Solo puede existir una regla de disponibilidad el mismo día para el mismo " +"tipo de habitación!" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "Only can payment by property" +msgstr "Solo puede pagar por propiedad" + +#. module: pms +#: code:addons/pms/models/pms_board_service_room_type.py:0 +#, python-format +msgid "Only can set one default board service" +msgstr "Solo se puede establecer un board service predeterminado" + +#. module: pms +#: code:addons/pms/wizards/wizard_several_partners.py:0 +#, python-format +msgid "Only one customer can be added to the reservation" +msgstr "Sólo se puede añadir un cliente a la reserva" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_bottom_tree +msgid "Open Reservation Room Detail" +msgstr "Detalles de la Habitación de la Reserva Abierta" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_open_several_partners_wizard +msgid "Open Several Partners" +msgstr "Abierto Varios Socios" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__operation +msgid "Operation" +msgstr "Operación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_split_join_swap_wizard__operation +msgid "Operation to be applied on the reservation" +msgstr "Operación a aplicar sobre la reserva" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_team_member__pms_role__manager +msgid "Operational Manager" +msgstr "Director de Operaciones" + +#. module: pms +#: model:ir.model,name:pms.model_pms_reservation_split_join_swap_wizard +msgid "Operations in reservations" +msgstr "Operaciones en reservas" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.report_folio_document +msgid "Order #" +msgstr "Pedido #" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__count +msgid "Order Count" +msgstr "Nº de Pedidos" + +#. module: pms +#: code:addons/pms/controllers/pms_portal.py:0 +#: model:ir.model.fields,field_description:pms.field_pms_folio__date_order +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folio +#, python-format +msgid "Order Date" +msgstr "Fecha del Pedido" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__date_order +msgid "Order date of reservation" +msgstr "Fecha de pedido de la reserva" + +#. module: pms +#: code:addons/pms/models/folio_sale_line.py:0 +#, python-format +msgid "Ordered Quantity: %(old_qty)s -> %(new_qty)s" +msgstr "Cantidad Pedida: %(old_qty)s -> %(new_qty)s" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__invoice_policy +msgid "" +"Ordered Quantity: Invoice quantities ordered by the customer.\n" +"Delivered Quantity: Invoice quantities delivered to the customer." +msgstr "" +"Cantidad Pedida: facturas ordenadas por el cliente.\n" +"Cantiadad entregada: facturas entregadas al cliente." + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_account_bank_statement_line__origin_agency_id +#: model:ir.model.fields,field_description:pms.field_account_move__origin_agency_id +#: model:ir.model.fields,field_description:pms.field_account_move_line__origin_agency_id +#: model:ir.model.fields,field_description:pms.field_account_payment__origin_agency_id +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__origin_agency_id +msgid "Origin Agency" +msgstr "Agencia de Origen" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_account_payment__origin_reference +msgid "Origin Reference" +msgstr "Referencia Original" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.view_account_invoice_filter +#: model_terms:ir.ui.view,arch_db:pms.view_account_move_filter +#: model_terms:ir.ui.view,arch_db:pms.view_account_payment_search +msgid "Originating agency" +msgstr "Agencia de origen" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_checkin_partner__gender__other +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Other" +msgstr "Otro" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Other data" +msgstr "Otro dato" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +#, python-format +msgid "Others" +msgstr "Otros" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_checkin_partner__state__done +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__state__done +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +msgid "Out" +msgstr "Fuera" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_booking_duplicate__reservation_type__out +#: model:ir.model.fields.selection,name:pms.selection__pms_booking_engine__reservation_type__out +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__reservation_type__out +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__reservation_type__out +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Out of Service" +msgstr "Fuera de Servicio" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Out service description" +msgstr "Descripción de Fuera de Servicio" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__outgoing_qty +msgid "Outgoing" +msgstr "" + +#. module: pms +#: code:addons/pms/models/account_move.py:0 +#, python-format +msgid "Outstanding credits" +msgstr "Créditos pendientes" + +#. module: pms +#: code:addons/pms/models/account_move.py:0 +#, python-format +msgid "Outstanding debits" +msgstr "Débitos pendientes" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__overbooking +msgid "Overbooking" +msgstr "Sobreventa" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Overbookings" +msgstr "Sobreventas" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__payment_state__overpayment +msgid "Overpayment" +msgstr "Pago en exceso" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Overpayment!" +msgstr "¡Pago en exceso!" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "" +"PART\n" +" OF TRAVELERS ENTRY" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "PART OF TRAVELERS ENTRY" +msgstr "PARTE DE ENTRADA DE VIAJEROS" + +#. module: pms +#: model:ir.model,name:pms.model_pms_folio +msgid "PMS Folio" +msgstr "FICHA" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.view_partner_property_form +msgid "PMS Invoice policy" +msgstr "Política de facturación PMS" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_pms_move_out_invoice_type +msgid "PMS Invoices" +msgstr "" + +#. module: pms +#: model:ir.ui.menu,name:pms.pms_management_menu +msgid "PMS Management" +msgstr "Gestión de Hotel" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_team_member__pms_role +msgid "PMS Role" +msgstr "Función de PMS" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.product_template_view_form +msgid "PMS Service" +msgstr "Servicio PMS" + +#. module: pms +#: model:ir.model,name:pms.model_pms_team_member +msgid "PMS Team Member" +msgstr "Miembro del Equipo PMS" + +#. module: pms +#: model:ir.actions.report,name:pms.action_report_pms_pro_forma_invoice +msgid "PRO-FORMA Invoice" +msgstr "Factura PRO-FORMA" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__payment_state__paid +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Paid" +msgstr "Pagado/a" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__invoices_paid +msgid "Paid Out" +msgstr "Pagado/a" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability__parent_avail_id +msgid "Parent Avail" +msgstr "Disponibilidad de los Padres" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__parent_id +msgid "Parent Property" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room__parent_id +msgid "Parent Room" +msgstr "Habitación de los Padres" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability__parent_avail_id +msgid "Parent availability for this availability" +msgstr "Disponibilidad de los padres para esta disponibilidad" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__parent_name +msgid "Parent name" +msgstr "Nombre del Padre" + +#. module: pms +#: model:pms.room.type,name:pms.pms_room_type_parking +#: model:pms.room.type.class,name:pms.pms_room_type_class_parking +#: model:product.product,name:pms.pms_room_type_parking_product_product +msgid "Parking" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Partial" +msgstr "Parcial" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__payment_state__partial +msgid "Partially Paid" +msgstr "Parcialmente Pagado" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__partner_id +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__partner_id +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__partner_id +#: model:ir.model.fields,field_description:pms.field_pms_folio__partner_id +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__partner_id +#: model_terms:ir.ui.view,arch_db:pms.booking_duplicate +#: model_terms:ir.ui.view,arch_db:pms.booking_engine +#: model_terms:ir.ui.view,arch_db:pms.view_partner_data_form +msgid "Partner" +msgstr "Cliente" + +#. module: pms +#: model:ir.model,name:pms.model_pms_checkin_partner +msgid "Partner Checkins" +msgstr "Checkins del Cliente" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__contract_ids +msgid "Partner Contracts" +msgstr "Contratos del Cliente" + +#. module: pms +#: model:ir.model,name:pms.model_res_partner_id_category +msgid "Partner ID Category" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_res_partner_id_number +msgid "Partner ID Number" +msgstr "Número empresa" + +#. module: pms +#: model:ir.model,name:pms.model_pms_several_partners_wizard +msgid "Partner Operations" +msgstr "Operaciones de Socio" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__partner_requests +msgid "Partner Requests" +msgstr "Notas del Cliente" + +#. module: pms +#: model:ir.model,name:pms.model_res_partner_category +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__segmentation_ids +#: model:ir.model.fields,help:pms.field_pms_booking_engine__segmentation_ids +msgid "Partner Tags" +msgstr "Cliente" + +#. module: pms +#: code:addons/pms/models/res_partner_id_number.py:0 +#, python-format +msgid "Partner already has this document type" +msgstr "El cliente ya tiene guardado este tipo de documento" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__partner_id +msgid "Partner associated with checkin partner" +msgstr "Partner relacionado con el checkin partner" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Partner contact name is required" +msgstr "Se requiere el nombre del cliente" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__residence_country_id +#: model:ir.model.fields,help:pms.field_res_partner__residence_country_id +#: model:ir.model.fields,help:pms.field_res_users__residence_country_id +msgid "Partner country of residence" +msgstr "País de residencia del socio" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__partner_name +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__partner_name +msgid "Partner name" +msgstr "nombre" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__partner_relationship +msgid "Partner relationship" +msgstr "Relación con el socio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__residence_state_id +#: model:ir.model.fields,help:pms.field_res_partner__residence_state_id +#: model:ir.model.fields,help:pms.field_res_users__residence_state_id +msgid "Partner state of residence" +msgstr "Estado de residencia del socio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__partner_id +#: model:ir.model.fields,help:pms.field_pms_booking_engine__partner_id +msgid "Partner who made the reservation" +msgstr "Cliente que ha realizado la reserva" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__same_vat_partner_id +msgid "Partner with same Tax ID" +msgstr "Cliente con el mismo NIF" + +#. module: pms +#: code:addons/pms/models/res_partner.py:0 +#, python-format +msgid "Partner without document identification, update vals %s" +msgstr "Socio sin identificación documental, actualizar vals %s" + +#. module: pms +#: model:res.partner.id_category,name:pms.document_type_passport +msgid "Passport" +msgstr "Pasaporte" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_folio_payment +#: model_terms:ir.ui.view,arch_db:pms.wizard_payment_folio_view_form +msgid "Pay" +msgstr "Pagar" + +#. module: pms +#: code:addons/pms/controllers/pms_portal.py:0 +#, python-format +msgid "Pay & Confirm" +msgstr "Pagar & Confirmar" + +#. module: pms +#: code:addons/pms/models/payment_transaction.py:0 +#: model_terms:ir.ui.view,arch_db:pms.portal_folio_payment +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_end +#, python-format +msgid "Pay Now" +msgstr "Pagar Ahora" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_folio_payment +msgid "Pay with" +msgstr "Pagar con" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__debit_limit +msgid "Payable Limit" +msgstr "Limite por pagar" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.wizard_payment_folio_view_form +msgid "Payment" +msgstr "Pago" + +#. module: pms +#: model:ir.model,name:pms.model_payment_acquirer +msgid "Payment Acquirer" +msgstr "Adquirente de Pagos" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_payment_folio +msgid "Payment Folio" +msgstr "Pagsr Ficha" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__property_payment_method_id +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__payment_method_id +msgid "Payment Method" +msgstr "Método de Pago" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Payment Mode" +msgstr "Modo de Pago" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +msgid "Payment Pending" +msgstr "Pendiente de Pago" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__reference +msgid "Payment Ref." +msgstr "Ref. del Pago." + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__folio_payment_state +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Payment State" +msgstr "Estado del Pago" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__payment_state +msgid "Payment Status" +msgstr "Estado del Pago" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__payment_term_id +msgid "Payment Terms" +msgstr "Términos del Pago" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__payment_token_ids +msgid "Payment Tokens" +msgstr "Token Pago" + +#. module: pms +#: model:ir.model,name:pms.model_payment_transaction +msgid "Payment Transaction" +msgstr "Transacción de pago" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__payment_term_id +msgid "Payment terms for current folio." +msgstr "Condiciones de pago para el folio actual." + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "Payment: %s by %s" +msgstr "Pago: %s por %s" + +#. module: pms +#: model:ir.model,name:pms.model_account_payment +#: model:ir.model,name:pms.model_wizard_payment_folio +#: model:ir.model.fields,help:pms.field_pms_folio__payment_ids +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Payments" +msgstr "Pagos" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__transaction_ids +msgid "Payments made through payment acquirer" +msgstr "Pagos efectuados a través de entidades adquirentes" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__pending_amount +#: model:ir.model.fields,field_description:pms.field_pms_reservation__folio_pending_amount +msgid "Pending Amount" +msgstr "Cantidad pendiente del Folio" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__count_rooms_pending_arrival +#: model:ir.model.fields,field_description:pms.field_pms_reservation__count_pending_arrival +msgid "Pending Arrival" +msgstr "Llegada Pendiente" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__checkins_ratio +msgid "Pending Arrival Ratio" +msgstr "Ratio de Llegadas Pendientes" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__ratio_checkin_data +msgid "Pending Checkin Data" +msgstr "Detos del Checkin Pendientes" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_checkin_partner__state__precheckin +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__state__confirm +msgid "Pending arrival" +msgstr "Llegada Pendiente" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_service__per_day +msgid "Per Day" +msgstr "Por día" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__commission_percent +msgid "Percentage corresponding to commission" +msgstr "Porcentaje de la comisión" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_cancelation_rule__penalty_late +msgid "" +"Percentage of the total price that partner has to pay in case of late arrival" +msgstr "" +"Porcentaje del precio total que el cliente tiene que pagar en caso de " +"llegada tardía" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_cancelation_rule__penalty_noshow +msgid "" +"Percentage of the total price that partner has to pay in case of no show" +msgstr "" +"Porcentaje del precio total que el cliente tiene que pagar en caso de no " +"presentarse" + +#. module: pms +#: code:addons/pms/models/pms_checkin_partner.py:0 +#, python-format +msgid "Personal data is missing for check-in" +msgstr "Faltan datos personales en el checkin" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Persons" +msgstr "Personas" + +#. module: pms +#: code:addons/pms/models/pms_room.py:0 +#, python-format +msgid "Persons can't be higher than room capacity (%s)" +msgstr "Las personas no pueden superar a la capacidad de la habitación (%s)" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__phone +#: model:ir.model.fields,field_description:pms.field_pms_property__phone +#: model:ir.model.fields,field_description:pms.field_res_partner__phone +#: model:ir.model.fields,field_description:pms.field_res_users__phone +msgid "Phone" +msgstr "Teléfono" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Phone (Optional)" +msgstr "Teléfono (Opcional)" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__plan_avail +msgid "Plan Avail" +msgstr "Plan de Disponibilidad" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "Please define an accounting sales journal for the company %s (%s)." +msgstr "Defina un diario de ventas contables para la compañía %s (%s)." + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_account_analytic_distribution__pms_property_id +#: model:ir.model.fields,field_description:pms.field_account_analytic_line__pms_property_id +#: model:ir.model.fields,field_description:pms.field_account_move_line__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__pms_property_id +msgid "Pms Property" +msgstr "Hotel" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__pos_categ_id +msgid "Point of Sale Category" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__access_url +#: model:ir.model.fields,field_description:pms.field_pms_folio__access_url +#: model:ir.model.fields,field_description:pms.field_pms_reservation__access_url +msgid "Portal Access URL" +msgstr "URL de acceso al portal" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__pos_order_ids +msgid "Pos Order" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__pos_order_count +msgid "Pos Order Count" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__checkin_partner_possible_customer_id +#: model:ir.model.fields,field_description:pms.field_res_partner__checkin_partner_possible_customer_id +#: model:ir.model.fields,field_description:pms.field_res_users__checkin_partner_possible_customer_id +msgid "Possible Customer In Checkin Partner" +msgstr "Posible Cliente en el Registro de Entrada Socio" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__folio_possible_customer_id +#: model:ir.model.fields,field_description:pms.field_res_partner__folio_possible_customer_id +#: model:ir.model.fields,field_description:pms.field_res_users__folio_possible_customer_id +msgid "Possible Customer In Folio" +msgstr "Posible Cliente En Folio" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__reservation_possible_customer_id +#: model:ir.model.fields,field_description:pms.field_res_partner__reservation_possible_customer_id +#: model:ir.model.fields,field_description:pms.field_res_users__reservation_possible_customer_id +msgid "Possible Customer In Reservation" +msgstr "Posible Cliente en Reserva" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__possible_existing_customer_ids +#: model:ir.model.fields,field_description:pms.field_pms_folio__possible_existing_customer_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__possible_existing_customer_ids +msgid "Possible existing customer" +msgstr "El cliente posiblemente ya exista registrado" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.several_partners_wizard +msgid "Possibles customers" +msgstr "Posibles clientes" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__state__draft +msgid "Pre-reservation" +msgstr "Pre-reserva" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation_precheckin +msgid "PreCheckin in" +msgstr "Registro de entrada previo" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__property_payment_method_id +msgid "" +"Preferred payment method when paying this vendor. This is used to filter " +"vendor bills by preferred payment method to register payments in mass. Use " +"cases: create bank files for batch wires, check runs." +msgstr "" +"Forma de pago preferida al pagar a este proveedor. Se utiliza para filtrar " +"facturas de proveedores por método de pago preferido para registrar pagos en " +"masa. Casos de uso: crear archivos bancarios para transferencias por lotes, " +"ejecuciones de cheques." + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__prepaid_warning_days +msgid "Prepaid Warning Days" +msgstr "Días de advertencia del prepago" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__price +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__price +#: model:ir.model.fields,field_description:pms.field_pms_room_type__price +msgid "Price" +msgstr "Precio" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__price_reduce +msgid "Price Reduce" +msgstr "Precio Reducido" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__price_reduce_taxexcl +msgid "Price Reduce Tax excl" +msgstr "Precio Reducido Impuestos Excluidos" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__price_reduce_taxinc +msgid "Price Reduce Tax inc" +msgstr "Precio Reducido Impuestos Incluidos" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation_detail +msgid "Price Total" +msgstr "Precio total" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Price and Availability Plans" +msgstr "Tarifa y Planes de Disponibilidad" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__list_price +#: model:ir.model.fields,help:pms.field_product_product__list_price +#: model:ir.model.fields,help:pms.field_product_template__list_price +msgid "Price at which the product is sold to customers." +msgstr "Precio cuando el producto es vendido a los clientes." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service_line__amount +msgid "Price for this Board Service Line/Product" +msgstr "Servicios incluidos en el Servicio de Habitación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service_room_type_line__amount +msgid "Price for this Board Service Room Type Line/Product" +msgstr "Pms Board Service Room Type" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service__amount +#: model:ir.model.fields,help:pms.field_pms_board_service_room_type__amount +msgid "" +"Price for this Board Service. It corresponds to the sum of his board service " +"lines" +msgstr "" +"Precio de este board service. Corresponde a la suma de sus board service " +"lines" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__price_per_room +msgid "Price per room" +msgstr "Precio poo habitación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio_availability_wizard__price_per_room +msgid "Price per room in folio" +msgstr "Precio por habitación en la Ficha" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service_line__price_unit +msgid "Price per unit of service" +msgstr "Precio ud de servicio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__price_reduce_taxinc +msgid "Price with discounts applied and taxes included" +msgstr "Precio con descuentos e impuestos incluidos" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__price_reduce_taxexcl +msgid "Price with discounts applied without taxes" +msgstr "Precio con descuentos sin impuestos" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__amount_tax +msgid "Price with taxes on a folio" +msgstr "Precio con impuestos de la ficha" + +#. module: pms +#: model:ir.model,name:pms.model_product_pricelist +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__pricelist_id +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__pricelist_id +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__pricelist_ids +#: model:ir.model.fields,field_description:pms.field_pms_folio__pricelist_id +#: model:ir.model.fields,field_description:pms.field_pms_property__property_product_pricelist +#: model:ir.model.fields,field_description:pms.field_pms_reservation__pricelist_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__pricelist_id +#: model:ir.model.fields,field_description:pms.field_pms_room_type__pricelist_id +#: model:ir.model.fields,field_description:pms.field_product_pricelist_item__pricelist_id +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__new_pricelist_id +#: model:ir.model.fields.selection,name:pms.selection__pms_massive_changes_wizard__massive_changes_on__pricelist +#: model_terms:ir.ui.view,arch_db:pms.booking_duplicate +#: model_terms:ir.ui.view,arch_db:pms.booking_engine +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Pricelist" +msgstr "Tarifa" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__pricelist_items_to_overwrite +msgid "Pricelist Items to Override" +msgstr "Items de la tarefa a sobrescribir" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__pricelist_readonly +msgid "Pricelist Readonly" +msgstr "Tarifas de solo lectura" + +#. module: pms +#: model:ir.model,name:pms.model_product_pricelist_item +msgid "Pricelist Rule" +msgstr "Regla de Tarifa" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_product_pricelist__pricelist_type +msgid "Pricelist Type" +msgstr "Tipo de Tarifa" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__pricelist_id +#: model:ir.model.fields,help:pms.field_pms_booking_engine__pricelist_id +msgid "Pricelist applied in folio" +msgstr "Tarifa para el Folio Actual." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__pricelist_id +msgid "Pricelist for current folio." +msgstr "Tarifa para el Folio Actual." + +#. module: pms +#: model:ir.model.fields,help:pms.field_product_pricelist_item__pricelist_id +msgid "Pricelist in which this item is included" +msgstr "Tarifa en la que se incluye este item" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__num_pricelist_items_to_overwrite +msgid "Pricelist items to overwrite on massive changes" +msgstr "Reglas de la tarifa que se sobrescribirán en los cambios" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__pricelist_id +msgid "Pricelist that guides the prices of the reservation" +msgstr "Tarifa que contiene los precios de la reserva" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_cancelation_rule__pricelist_ids +msgid "Pricelist that use this rule" +msgstr "Tarifas que usan esta regla" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__pricelist_ids +msgid "Pricelist to apply massive changes" +msgstr "Tarifas a aplicar en los cambios masivos" + +#. module: pms +#: model:ir.model.fields,help:pms.field_product_pricelist__pricelist_type +msgid "Pricelist types, it can be Daily Plan" +msgstr "Tipos de lista de precios, puede ser Plan diario" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_duplicate__pricelist_id +msgid "Pricelist used for this reservation" +msgstr "Lista de precios utilizada para esta reserva" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan__pms_pricelist_ids +#: model:ir.model.fields,field_description:pms.field_pms_sale_channel__product_pricelist_ids +#: model:ir.ui.menu,name:pms.pricelist_menu +msgid "Pricelists" +msgstr "Tarifas" + +#. module: pms +#: model:ir.actions.act_window,name:pms.product_pricelist_item_action2 +msgid "Pricelists Items" +msgstr "Reglas de tarifa" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_sale_channel__product_pricelist_ids +msgid "Pricelists for a sale channel" +msgstr "Tarifas para un canal de venta" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan__pms_pricelist_ids +msgid "Pricelists of the availability plan " +msgstr "Tarifa del plan de disponibilidad" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "" +"Prices have been recomputed according to pricelist %s\n" +" and room type %s" +msgstr "" +"Los precios se han vuelto a calcular de acuerdo con la lista de precios" +"%s\n" +" y tipo de habitación %s" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.folio_portal_template +msgid "Print" +msgstr "Imprimir" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Print All Checkins" +msgstr "Imprimir todos los Checkins" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_form +msgid "Print in PDF" +msgstr "Imprimir PDF" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Print in cardex" +msgstr "Imprimir en cardex" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__priority +#: model:ir.model.fields,field_description:pms.field_res_country__priority +#: model:ir.model.fields,field_description:pms.field_res_partner_id_category__priority +msgid "Priority" +msgstr "Prioridad" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__priority +msgid "Priority of a reservation" +msgstr "Unificar la reserva" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__privacy_policy +#: model:ir.model.fields,field_description:pms.field_res_company__privacy_policy +#: model_terms:ir.ui.view,arch_db:pms.company_view_form +msgid "Privacy Policy" +msgstr "Política de privacidad" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_amenity_view_form +msgid "Procurement" +msgstr "Obtención" + +#. module: pms +#: model:ir.model,name:pms.model_product_product +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__product_id +#: model:ir.model.fields,field_description:pms.field_pms_board_service_line__product_id +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type_line__product_id +#: model:ir.model.fields,field_description:pms.field_pms_room_type__product_variant_id +#: model:ir.model.fields,field_description:pms.field_pms_service_line__product_id +#: model:ir.model.fields,field_description:pms.field_product_pricelist_item__product_id +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "Product" +msgstr "Producto" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__attribute_line_ids +msgid "Product Attributes" +msgstr "Atributos del Producto" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__categ_id +msgid "Product Category" +msgstr "Categoría del Producto" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_service__product_image +msgid "Product Image" +msgstr "Imagen del Producto" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__packaging_ids +msgid "Product Packages" +msgstr "Paquetes del Producto" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__default_pricelist_id +msgid "Product Pricelist" +msgstr "Tarifa del Producto" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__product_id +msgid "Product Room Type" +msgstr "Producto del Tipo de Habitación" + +#. module: pms +#: model:ir.model,name:pms.model_product_template +#: model:ir.model.fields,field_description:pms.field_pms_room_type__product_tmpl_id +#: model:ir.model.fields,field_description:pms.field_product_pricelist_item__product_tmpl_id +msgid "Product Template" +msgstr "Plantilla de producto" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__type +msgid "Product Type" +msgstr "Tipo de Producto" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__product_uom_readonly +msgid "Product Uom Readonly" +msgstr "Producto UOM solo lectura" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__product_id +msgid "" +"Product associated with folio sale line, can be product associated with " +"service or product associated withreservation's room type, in other case " +"it's false" +msgstr "" +"Producto asociado a línea de venta de folio, puede ser producto asociado a " +"servicio o producto asociado al tipo de habitación de la reserva, en caso " +"contrario es falso" + +#. module: pms +#: model:ir.model.fields,help:pms.field_product_pricelist_item__product_id +msgid "Product associated with the item" +msgstr "Producto asociado con el item" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service_line__product_id +msgid "Product associated with this board service line" +msgstr "Producto asociado con este board service line" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service_room_type_line__product_id +msgid "Product associated with this board service room type line" +msgstr "Producto asociado con este board service room type line" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__product_id +msgid "Product associated with this service" +msgstr "Producto asociado a este servicio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service_line__product_id +msgid "Product associated with this service line" +msgstr "Producto asociado a esta línea de servicio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__product_id +msgid "Product identifier associated with room type" +msgstr "Identificador de producto asociado al tipo de habitación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_product_pricelist_item__product_tmpl_id +msgid "Product template associated with the item" +msgstr "Product template asociada con el item" + +#. module: pms +#: model:ir.model.fields,help:pms.field_res_company__cancel_penalty_product_id +msgid "Product used to calculate the cancel penalty" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__property_stock_production +msgid "Production Location" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__product_variant_ids +msgid "Products" +msgstr "Prductos" + +#. module: pms +#: model:ir.actions.act_window,name:pms.pms_property_action +#: model:ir.model.fields,field_description:pms.field_account_journal__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_ir_pms_property__pms_property_id +#: model:ir.model.fields,field_description:pms.field_payment_acquirer__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_pms_amenity__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_pms_amenity_type__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_pms_board_service__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_pms_board_service_line__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_pms_property__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_pms_room_type__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_pms_sale_channel__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_pms_ubication__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_product_pricelist__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_product_pricelist_item__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_product_product__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_product_template__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_res_company__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_res_partner__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_res_users__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_room_closure_reason__pms_property_ids +#: model:ir.ui.menu,name:pms.general_property_menu +#: model:ir.ui.menu,name:pms.pms_property_menu +msgid "Properties" +msgstr "Hoteles" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_bank_statement__pms_property_id +#: model:ir.model.fields,help:pms.field_pms_availability_plan_rule__pms_property_id +#: model:ir.model.fields,help:pms.field_res_company__pms_property_ids +msgid "Properties with access to the element" +msgstr "Propiedades con acceso al elemento" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__pms_property_ids +#: model:ir.model.fields,help:pms.field_res_partner__pms_property_ids +msgid "" +"Properties with access to the element if not set, all properties can access" +msgstr "" +"Propiedades con acceso al elemento si no se establece, todas las propiedades " +"pueden acceder a" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_journal__pms_property_ids +#: model:ir.model.fields,help:pms.field_payment_acquirer__pms_property_ids +#: model:ir.model.fields,help:pms.field_pms_amenity__pms_property_ids +#: model:ir.model.fields,help:pms.field_pms_amenity_type__pms_property_ids +#: model:ir.model.fields,help:pms.field_pms_availability_plan__pms_property_ids +#: model:ir.model.fields,help:pms.field_pms_board_service__pms_property_ids +#: model:ir.model.fields,help:pms.field_pms_board_service_line__pms_property_ids +#: model:ir.model.fields,help:pms.field_pms_cancelation_rule__pms_property_ids +#: model:ir.model.fields,help:pms.field_pms_room__pms_property_id +#: model:ir.model.fields,help:pms.field_pms_room_type__pms_property_ids +#: model:ir.model.fields,help:pms.field_pms_room_type_class__pms_property_ids +#: model:ir.model.fields,help:pms.field_pms_sale_channel__pms_property_ids +#: model:ir.model.fields,help:pms.field_pms_ubication__pms_property_ids +#: model:ir.model.fields,help:pms.field_product_pricelist__pms_property_ids +#: model:ir.model.fields,help:pms.field_product_pricelist_item__pms_property_ids +#: model:ir.model.fields,help:pms.field_product_product__pms_property_ids +#: model:ir.model.fields,help:pms.field_product_template__pms_property_ids +#: model:ir.model.fields,help:pms.field_room_closure_reason__pms_property_ids +msgid "" +"Properties with access to the element; if not set, all properties can access" +msgstr "" +"Propiedades con acceso al elemento; si no se establece, todas las " +"propiedades pueden acceder" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio_availability_wizard__pms_property_id +#: model:ir.model.fields,help:pms.field_pms_reservation_duplicate__pms_property_id +msgid "Propertiy with access to the element;" +msgstr "Propiedad con acceso al elemento;" + +#. module: pms +#. openerp-web +#: code:addons/pms/static/src/js/reconciliation_widget.js:0 +#: code:addons/pms/static/src/xml/account_reconciliation.xml:0 +#: model:ir.model,name:pms.model_pms_property +#: model:ir.model.fields,field_description:pms.field_account_bank_statement__pms_property_id +#: model:ir.model.fields,field_description:pms.field_account_bank_statement_line__pms_property_id +#: model:ir.model.fields,field_description:pms.field_account_move__pms_property_id +#: model:ir.model.fields,field_description:pms.field_account_payment__pms_property_id +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_availability__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type_line__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_folio__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_pms_property__partner_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_room__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_service__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_service_line__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_team_member__pms_property_id +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_room_type_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +#: model_terms:ir.ui.view,arch_db:pms.view_account_invoice_filter +#: model_terms:ir.ui.view,arch_db:pms.view_account_journal_search +#: model_terms:ir.ui.view,arch_db:pms.view_account_move_filter +#: model_terms:ir.ui.view,arch_db:pms.view_account_move_line_filter +#: model_terms:ir.ui.view,arch_db:pms.view_account_payment_search +#, python-format +msgid "Property" +msgstr "Hotel" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__pms_property_code +msgid "Property Code" +msgstr "Código de Propiedad" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Property Configuration" +msgstr "Configuración del Hotel" + +#. module: pms +#: model:res.groups,name:pms.group_pms_call +msgid "Property Management / CallCenter" +msgstr "Gestión Hotel/ Centro de Llamadas" + +#. module: pms +#: model:res.groups,name:pms.group_pms_user +msgid "Property Management / User" +msgstr "Gestión Hotel/ Usuarios" + +#. module: pms +#: model:res.groups,name:pms.group_pms_manager +msgid "Property Management/ Manager" +msgstr "Gestión Hotel/ Gerente" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__res_partner__invoicing_policy__property +msgid "Property Policy Invoice" +msgstr "Factura de Póliza de Propiedad" + +#. module: pms +#: model:ir.model,name:pms.model_pms_room +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_form +msgid "Property Room" +msgstr "Habitación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room__room_type_id +#: model_terms:ir.ui.view,arch_db:pms.pms_room_type_view_form +msgid "Property Room Type" +msgstr "Tipo de Habitación del Hotel" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__class_id +msgid "Property Type Class" +msgstr "Clase del Tipo del Hotel" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_ubication_view_form +msgid "Property Ubication" +msgstr "Ubicación del Hotel" + +#. module: pms +#: code:addons/pms/models/pms_ubication.py:0 +#, python-format +msgid "Property not allowed" +msgstr "Hotel no permitido" + +#. module: pms +#: code:addons/pms/models/pms_availability.py:0 +#, python-format +msgid "Property not allowed on availability day compute" +msgstr "Propiedad no permitida en el cálculo del día de disponibilidad" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability__pms_property_id +msgid "Property to which the availability is directed" +msgstr "Propiedad a la que corresponde la disponibilidad" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__pms_property_id +msgid "Property to which the folio associated belongs" +msgstr "Hotel al que pertenece la ficha" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__pms_property_id +#: model:ir.model.fields,help:pms.field_pms_booking_engine__pms_property_id +msgid "Property to which the folio belongs" +msgstr "Propiedad a la que pertenece la ficha" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__pms_property_id +msgid "Property to which the reservation belongs" +msgstr "Propiedad a la que pertenece la reserva" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__pms_property_id +#: model:ir.model.fields,help:pms.field_pms_service_line__pms_property_id +msgid "Property to which the service belongs" +msgstr "Propiedad a la que pertenece el servicio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_line__pms_property_id +msgid "Property with access to the element" +msgstr "Propiedad con acceso al elemento" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__pms_property_id +msgid "Property with access to the element;" +msgstr "Propiedad con acceso al elemento;" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service_room_type_line__pms_property_id +msgid "" +"Property with access to the element; if not set, all properties can access" +msgstr "" +"Propiedad con acceso al elemento; si no se establece, todas las propiedades " +"pueden acceder" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service_room_type__pms_property_id +msgid "" +"Property with access to the element; if not set, all property can access" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__ratio_checkin_data +msgid "Proportion of guest data complete at checkin" +msgstr "Proporción de datos de clientes completados al momento del check-in" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__checkins_ratio +msgid "Proportion of guest pending checkin" +msgstr "Proporción de clientes pendientes de check in" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__lst_price +msgid "Public Price" +msgstr "Precio Público" + +#. module: pms +#: model:product.pricelist,name:pms.pricelist_discount_10_percent +msgid "Public Pricelist Discount 10%" +msgstr "Lista de Precios Públicos 10% de descuento" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__description_purchase +msgid "Purchase Description" +msgstr "Descripción de Compra" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__uom_po_id +msgid "Purchase Unit of Measure" +msgstr "Unidad de medida de la compra" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__putaway_rule_ids +msgid "Putaway Rules" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_service_line__auto_qty +msgid "Qty automated setted" +msgstr "Cantidad configurada automáticamente" + +#. module: pms +#: model:pms.room.type,name:pms.pms_room_type_quadruple +#: model:product.product,name:pms.pms_room_type_quadruple_product_product +msgid "Quadruple" +msgstr "Cuádruple" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__product_uom_qty +#: model:ir.model.fields,field_description:pms.field_pms_service__product_qty +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation_detail +#: model_terms:ir.ui.view,arch_db:pms.report_folio_document +msgid "Quantity" +msgstr "Cantidad" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__qty_available +msgid "Quantity On Hand" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__quantity_svl +msgid "Quantity Svl" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__incoming_qty +msgid "" +"Quantity of planned incoming products.\n" +"In a context with a single Stock Location, this includes goods arriving to " +"this Location, or any of its children.\n" +"In a context with a single Warehouse, this includes goods arriving to the " +"Stock Location of this Warehouse, or any of its children.\n" +"Otherwise, this includes goods arriving to any Stock Location with " +"'internal' type." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__outgoing_qty +msgid "" +"Quantity of planned outgoing products.\n" +"In a context with a single Stock Location, this includes goods leaving this " +"Location, or any of its children.\n" +"In a context with a single Warehouse, this includes goods leaving the Stock " +"Location of this Warehouse, or any of its children.\n" +"Otherwise, this includes goods leaving any Stock Location with 'internal' " +"type." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__day_qty +msgid "Quantity per day" +msgstr "Cantidad por día" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__visible_qty_configurator +msgid "Quantity visible in configurator" +msgstr "Cantidad visible en configurador" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__quota +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__quota +msgid "Quota" +msgstr "Cupo" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__default_quota +msgid "" +"Quota assigned to the own Booking Engine given no availability rules. Use " +"`-1` for managing no quota." +msgstr "" +"Cupo asignada al propio Motor de Reservas sin reglas de disponibilidad. " +"Utilice '-1' para gestionar la ausencia de cuota." + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__state__draft +msgid "Quotation" +msgstr "Presupuesto" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.report_folio_document +msgid "Quotation #" +msgstr "Presupuesto #" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__state__sent +msgid "Quotation Sent" +msgstr "Presupuesto Envíado" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__expense_policy +msgid "Re-Invoice Expenses" +msgstr "Gastos de re-facturación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__visible_expense_policy +msgid "Re-Invoice Policy visible" +msgstr "Pol" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__ready_for_checkin +msgid "Ready for checkin" +msgstr "Listo para checkin" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability__real_avail +msgid "Real Avail" +msgstr "Disponibilidad Real" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__real_avail +msgid "Real availability" +msgstr "Dispo Real" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.booking_duplicate +#: model_terms:ir.ui.view,arch_db:pms.booking_engine +msgid "Reason" +msgstr "Motivo" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__cancelled_reason +msgid "Reason of cancellation" +msgstr "Causa de la Cancelación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__closure_reason_id +msgid "Reason why the reservation cannot be made" +msgstr "Motivo por el que no se puede realizar la reserva" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_team_member__pms_role__reception +msgid "Reception" +msgstr "Recepción" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__user_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__user_id +msgid "Reception Manager" +msgstr "Jefe de Recepción" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_advanced_filters_wizard__pms_model_id +msgid "Recipients Model" +msgstr "Modelos destinatarios" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_advanced_filters_wizard__pms_model_name +msgid "Recipients Model Name" +msgstr "Nombre del Modelo de destinatarios" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__recompute_prices +msgid "Recompute Price" +msgstr "Recalcular el Precio" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Recompute all prices based on this pricelist" +msgstr "Recalcular precios basados en la tarifa" + +#. module: pms +#: model:ir.actions.server,name:pms.priority_reservations_ir_actions_server +#: model:ir.cron,cron_name:pms.priority_reservations +#: model:ir.cron,name:pms.priority_reservations +msgid "Recompute priority on reservations" +msgstr "Recalcular prioridad en reservas" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_ir_pms_property__record +msgid "Record Id" +msgstr "Registro Id" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__price_reduce +msgid "Reduced price amount, that is, total price with discounts applied" +msgstr "" +"Importe del precio reducido, es decir, precio total con los descuentos " +"aplicados" + +#. module: pms +#: code:addons/pms/controllers/pms_portal.py:0 +#: model:ir.model.fields,field_description:pms.field_pms_property__ref +#: model:ir.model.fields,field_description:pms.field_pms_room_type__code +#, python-format +msgid "Reference" +msgstr "Referencia" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_ir_pms_property__value_reference +msgid "Reference Field Value" +msgstr "Valor del Campo de Referencia" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__external_reference +#: model:ir.model.fields,help:pms.field_pms_reservation__external_reference +msgid "Reference of this folio in an external system" +msgstr "Referencia de este folio en un sistema externo" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "Refund: %s by %s" +msgstr "Reembolso: %s por %s" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Register Checkins" +msgstr "Registro de checkin" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Register Partners" +msgstr "Registro de clientes" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "Register Payment" +msgstr "Registro de pago" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__folio_advance_payment_inv__advance_payment_method__delivered +msgid "Regular invoice" +msgstr "Factura normal" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Remove some of the leftover assigned checkins first" +msgstr "Elimine primero algunos de los registros sobrantes" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__reordering_max_qty +msgid "Reordering Max Qty" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__reordering_min_qty +msgid "Reordering Min Qty" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__nbr_reordering_rules +msgid "Reordering Rules" +msgstr "" + +#. module: pms +#: model:ir.actions.report,name:pms.action_report_folio +msgid "Report Folio" +msgstr "Informe de Ficha" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__is_reselling +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__is_reselling +msgid "Reselling" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_resend_cancellation_email +msgid "Resend cancellation email" +msgstr "Reenviar correo electrónico de cancelación" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_resend_confirmation_email +msgid "Resend confirmation email" +msgstr "Reenviar correo electrónico de confirmación" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_resend_modification_email +msgid "Resend modification email" +msgstr "Reenviar correo electrónico de modificación" + +#. module: pms +#: model:ir.actions.act_window,name:pms.open_pms_reservation_form_tree_all +#: model:ir.model,name:pms.model_pms_reservation +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__reservation_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__reservation_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__reservation_id +#: model:ir.model.fields,field_description:pms.field_pms_service_line__reservation_id +#: model:ir.model.fields,field_description:pms.field_pms_several_partners_wizard__reservation_id +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +#: model_terms:ir.ui.view,arch_db:pms.reservation_wizard +msgid "Reservation" +msgstr "Reserva" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation +msgid "Reservation #" +msgstr "Reserva #" + +#. module: pms +#: model:ir.actions.act_window,name:pms.availability_action +msgid "Reservation Availability Plans" +msgstr "Planes de Disponibilidad" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__name +msgid "Reservation Code" +msgstr "Código de Reserva" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__name +msgid "Reservation Code Identification" +msgstr "Identificación del Código de Reserva" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Reservation Detail" +msgstr "Detalle de la Reserva" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Reservation Details" +msgstr "Detalles de la Reserva" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__reservation_order +msgid "Reservation Id" +msgstr "Id reserva" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_end +msgid "Reservation Info" +msgstr "Info Reserva" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability__reservation_line_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__reservation_line_ids +msgid "Reservation Lines" +msgstr "Líneas de la Reserva" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Reservation Notes" +msgstr "Notas de la Reserva" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__reservation_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__reference_reservation_id +msgid "Reservation Reference" +msgstr "Referencia de la Reserva" + +#. module: pms +#: model:ir.ui.menu,name:pms.menu_reservation_rooms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Reservation Rooms" +msgstr "Reservas" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_service_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_service_view_search +msgid "Reservation Service" +msgstr "Servicios de la Reserva" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Reservation Total" +msgstr "Total de la Reserva" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__reservation_type +msgid "Reservation Type" +msgstr "Tipo de Reserva" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_wizard_reservation_lines_split__reservation_wizard_id +msgid "Reservation Wizard" +msgstr "Asistnte Reserva" + +#. module: pms +#: model:ir.model,name:pms.model_pms_availability_plan +msgid "Reservation availability plan" +msgstr "Plan de Disponibilidad de la Reserva" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Reservation dates should be consecutives" +msgstr "Las fechas de reserva deben ser consecutivas." + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#: code:addons/pms/models/pms_reservation.py:0 +#: code:addons/pms/wizards/pms_booking_duplicate.py:0 +#: code:addons/pms/wizards/pms_booking_engine.py:0 +#, python-format +msgid "Reservation from " +msgstr "Reserva desde" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__reservation_id +msgid "Reservation in which the service is included" +msgstr "Reserva en la que se incluye el servicio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__reservation_line_ids +msgid "" +"Reservation lines associated with folio sale line, they corresponds with " +"nights" +msgstr "Líneas de reserva asociadas al folio sale line, corresponden a noches" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_view_reservation_operations +msgid "Reservation operations" +msgstr "Operaciones sobre la reserva" + +#. module: pms +#: model:ir.model,name:pms.model_pms_availability_plan_rule +msgid "Reservation rule by day" +msgstr "Reglas de reserva por día" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_duplicate__reference_reservation_id +msgid "Reservation to copy data" +msgstr "Reserva para copiar datos" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__reservation_id +msgid "Reservation to which checkin partners belong" +msgstr "Reserva a la que pertenecen los checkin partners" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__reservation_id +msgid "Reservation to which folio sale line belongs" +msgstr "Reserva a la que pertenece el folio sale line " + +#. module: pms +#: model:ir.actions.act_window,name:pms.pms_partner_reservations +#: model:ir.model.fields,field_description:pms.field_account_bank_statement_line__reservation_ids +#: model:ir.model.fields,field_description:pms.field_pms_folio__reservation_ids +#: model:ir.model.fields,field_description:pms.field_pms_property__pms_reservation_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__reservation_ids +#: model:ir.model.fields,field_description:pms.field_res_partner__pms_reservation_ids +#: model:ir.model.fields,field_description:pms.field_res_users__pms_reservation_ids +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__reservation_ids +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__reservation_ids +#: model:ir.model.fields.selection,name:pms.selection__wizard_folio_changes__modification_type__reservations +#: model:ir.ui.menu,name:pms.menu_reservations +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_graph +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_pivot +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_calendar +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_timeline +#: model_terms:ir.ui.view,arch_db:pms.portal_my_home_menu_reservation +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservations +#: model_terms:ir.ui.view,arch_db:pms.res_partner_view_form +msgid "Reservations" +msgstr "Reservas" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__reservation_lines_to_change +msgid "Reservations Lines To Change" +msgstr "Líneas a cambiar de la reserva" + +#. module: pms +#: model:ir.model,name:pms.model_pms_reservation_line +msgid "Reservations by day" +msgstr "Reservas por día" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_bank_statement_line__reservation_ids +msgid "Reservations in which the Account Bank Statement Lines are included" +msgstr "" +"Reservas en las que se incluyen las líneas del extracto bancario de la cuenta" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.res_partner_view_form +msgid "Reservations related with this contact" +msgstr "Reservas relacionadas con este contacto" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Reservations to 1 month" +msgstr "Reservas a 1 mes" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Reservations to 14 days" +msgstr "Reservas a 14 días" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Reservations to 7 days" +msgstr "Reservas a 7 días" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_home_menu_reservation +msgid "Reservations/" +msgstr "Reservas/" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_form +#: model_terms:ir.ui.view,arch_db:pms.res_partner_view_form +msgid "Residence Address" +msgstr "Dirección de Residencia" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Residence Address *" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__responsible_id +msgid "Responsible" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__activity_user_id +#: model:ir.model.fields,field_description:pms.field_pms_folio__activity_user_id +#: model:ir.model.fields,field_description:pms.field_pms_property__activity_user_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__activity_user_id +#: model:ir.model.fields,field_description:pms.field_pms_room_type__activity_user_id +msgid "Responsible User" +msgstr "Usuario Responsable" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_team_member__pms_role__revenue +msgid "Revenue" +msgstr "Ingresos" + +#. module: pms +#: model:ir.ui.menu,name:pms.revenue_management_menu +msgid "Revenue Management" +msgstr "Revenue" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__revenue_user_id +msgid "Revenue Manager" +msgstr "Administrador de Ingresos" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__preferred_room_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__preferred_room_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__room_id +#: model:ir.model.fields,field_description:pms.field_pms_service__reservation_id +#: model:ir.model.fields,field_description:pms.field_pms_wizard_reservation_lines_split__room_id +#: model:pms.room.type.class,name:pms.pms_room_type_class_bedroom +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_search +#: model_terms:ir.ui.view,arch_db:pms.reservation_wizard +msgid "Room" +msgstr "Habitación" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_pms_room_amenity_view_form +#: model:ir.model.fields,field_description:pms.field_pms_room__room_amenity_ids +#: model_terms:ir.ui.view,arch_db:pms.pms_amenity_search +#: model_terms:ir.ui.view,arch_db:pms.pms_amenity_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_room_type_view_form +msgid "Room Amenities" +msgstr "Características de la habitación" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_room_amenity_type_view_form +msgid "Room Amenities Type" +msgstr "Tipo de características de la habitación" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_room_type_class_view_form +msgid "Room Class" +msgstr "Clase de Habitación" + +#. module: pms +#: model:ir.actions.act_window,name:pms.open_pms_room_closure_reason_form_tree +#: model_terms:ir.ui.view,arch_db:pms.pms_room_closure_reason_view_form +msgid "Room Closure Reason" +msgstr "Razón de cierre de la habitación" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Room Line" +msgstr "Línea de la habitación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room__name +#: model:ir.model.fields,help:pms.field_pms_room__name +msgid "Room Name" +msgstr "Nombre de la Habitación" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_kanban_view +msgid "Room Reservation" +msgstr "Reserva" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__price_room_services_set +msgid "Room Services Total" +msgstr "Servicios totales de la habitación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__room_source +msgid "Room Source" +msgstr "Habitación Origen" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__room_target +msgid "Room Target" +msgstr "Habitación destino" + +#. module: pms +#: model:ir.actions.act_window,name:pms.open_pms_room_type_form_tree +#: model:ir.model,name:pms.model_pms_room_type +#: model:ir.model.fields,field_description:pms.field_pms_availability__room_type_id +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__room_type_id +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type__pms_room_type_id +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__room_type_id +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__room_type_ids +#: model:ir.model.fields,field_description:pms.field_pms_num_rooms_selection__room_type_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__room_type_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__room_type_id +#: model:ir.model.fields,field_description:pms.field_pms_room_type__room_type_id +#: model:ir.model.fields,field_description:pms.field_product_product__room_type_id +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_room_type_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_search +msgid "Room Type" +msgstr "Tipo de Habitación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__room_amenity_ids +msgid "Room Type Amenities" +msgstr "Características del tipo de habitación" + +#. module: pms +#: model:ir.actions.act_window,name:pms.open_pms_room_type_class_form_tree +#: model:ir.model,name:pms.model_pms_room_type_class +#: model:ir.ui.menu,name:pms.menu_open_pms_room_type_class_form_tree +msgid "Room Type Class" +msgstr "Clase del Tipo de Habitación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service_room_type__pms_room_type_id +msgid "Room Type for which this Board Service is available" +msgstr "Tipo de habitación para el que está disponible este board service" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio_availability_wizard__room_type_id +#: model:ir.model.fields,help:pms.field_pms_reservation_duplicate__room_type_id +msgid "Room Type reserved" +msgstr "Tipo de habitación vendida" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__room_type_id +msgid "" +"Room Type sold on the reservation,it doesn't necessarily correspond to the " +"room actually assigned" +msgstr "" +"Tipo de habitación vendido en la reserva, no necesariamente corresponde a la " +"habitación realmente asignada" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_kanban +msgid "Room Type:" +msgstr "Tipo de Habitación:" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_massive_changes_wizard__apply_pricelists_on__room_types +#: model:ir.ui.menu,name:pms.menu_open_pms_room_type_form_tree +msgid "Room Types" +msgstr "Tipos de Habitación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type_class__room_type_ids +msgid "Room Types that belong to this Room Type Class" +msgstr "Tipos de habitación que pertenecen a esta clase del tipo de habitación" + +#. module: pms +#: model:ir.model,name:pms.model_pms_amenity +msgid "Room amenity" +msgstr "Característica de la habitación" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_pms_room_amenity_type_view_form +msgid "Room amenity Type" +msgstr "Tipo de característica de la habitación" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "" +"Room line Check In Date Should be less than the Check " +"Out Date!" +msgstr "" +"¡El checkin de la línea de habitación debe ser inferior que la fecha de " +"salida!" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__reservation_ids +msgid "Room reservation detail" +msgstr "Detalles de la reserva" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_duplicate__preferred_room_id +msgid "Room reserved" +msgstr "Habitación reservada" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service_line__reservation_id +msgid "Room to which the services will be applied" +msgstr "Habitación a la que se aplicarán los servicios" + +#. module: pms +#: model:ir.model,name:pms.model_pms_availability +msgid "Room type availability per day" +msgstr "Disponibilidad por día" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type_class__default_code +msgid "Room type class identification code" +msgstr "Código de identificación de clase de tipo de habitación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability__room_type_id +msgid "Room type for which availability is indicated" +msgstr "Tipo de habitación para la que se indica disponibilidad" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan_rule__room_type_id +msgid "Room type for which availability rule is applied" +msgstr "Tipo de habitación para el que se aplica la regla de disponibilidad" + +#. module: pms +#: model:ir.model,name:pms.model_pms_folio_availability_wizard +msgid "Room type line in Booking Engine" +msgstr "Línea de tipo de habitación en el Motor de Reservas" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__board_service_room_type_ids +msgid "Room type's board services" +msgstr "Board services por tipo de habitación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__room_type_filter_ids +msgid "Room types" +msgstr "Tipo de habitación" + +#. module: pms +#: code:addons/pms/wizards/wizard_split_join_swap_reservation.py:0 +#, python-format +msgid "Room {} not available." +msgstr "Habitación {} no disponible." + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__rooms +msgid "Room/s" +msgstr "Habitación/es" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Room:" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Rooming" +msgstr "Huéspedes/Rooming" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__line_ids +#: model:ir.model.fields,field_description:pms.field_pms_property__room_ids +#: model:ir.model.fields,field_description:pms.field_pms_room_type__room_ids +#: model:ir.model.fields,field_description:pms.field_pms_ubication__pms_room_ids +#: model:ir.ui.menu,name:pms.menu_open_pms_room_form +#: model:ir.ui.menu,name:pms.pms_rooms_menu +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_bottom_tree +msgid "Rooms" +msgstr "Habitaciones" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__free_room_ids +msgid "Rooms available" +msgstr "Habitaciones disponibles" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_ubication__pms_room_ids +msgid "Rooms found in this location" +msgstr "Habitaciones en esta ubicación" + +#. module: pms +#: model:ir.model,name:pms.model_pms_reservation_duplicate +msgid "Rooms in Duplicate Folio" +msgstr "Habitaciones en Folio Duplicado" + +#. module: pms +#: code:addons/pms/models/pms_availability.py:0 +#, python-format +msgid "Rooms shared between different properties" +msgstr "Habitaciones compartidas entre diferentes propiedades" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__room_ids +msgid "Rooms that a property has." +msgstr "Habitaciones de una propiedad." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__rooms +msgid "Rooms that are reserved" +msgstr "Habitaciones resevadas" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__room_ids +msgid "Rooms that belong to room type." +msgstr "Habitaciones que pertenecen al tipo de habitación." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__line_ids +msgid "Rooms to create" +msgstr "Salas para crear" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__route_ids +msgid "Routes" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__has_available_route_ids +msgid "Routes can be selected on this product" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__rules_to_overwrite +msgid "Rule to Overwrite" +msgstr "Reglas a sobreescribir" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.availability_view_form +msgid "Rules" +msgstr "Reglas" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan__rule_ids +msgid "Rules in a availability plan" +msgstr "Plan de Disponibilidad de la Reserva" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__num_rules_to_overwrite +msgid "Rules to overwrite on massive changes" +msgstr "Reglas para sobrescribir en los cambios masivos" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__sii_exempt_cause +msgid "SII Exempt Cause" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.open_pms_sale_channel_form_tree +#: model:ir.model.fields,field_description:pms.field_pms_property__sale_channel_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__sale_channel_id +#: model:ir.model.fields,field_description:pms.field_res_partner__sale_channel_id +#: model:ir.model.fields,field_description:pms.field_res_users__sale_channel_id +#: model_terms:ir.ui.view,arch_db:pms.pms_sale_channel_view_form +msgid "Sale Channel" +msgstr "Canal de Venta" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_sale_channel__name +msgid "Sale Channel Name" +msgstr "Nombre del Canal de Venta" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__sale_channel_origin_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__sale_channel_origin_id +#: model:ir.model.fields,field_description:pms.field_pms_service__sale_channel_origin_id +msgid "Sale Channel Origin" +msgstr "Canal de Venta Origen" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_sale_channel__channel_type +msgid "Sale Channel Type" +msgstr "Tipo del Canal de Venta" + +#. module: pms +#: code:addons/pms/models/res_partner.py:0 +#, python-format +msgid "Sale Channel for an agency must be indirect" +msgstr "El canal de venta para una agencia debe ser indirecto" + +#. module: pms +#: code:addons/pms/models/res_partner.py:0 +#, python-format +msgid "Sale Channel must be entered" +msgstr "El Canal de Venta debe ser Indicado" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__sale_channel_origin_id +msgid "Sale Channel through which folio was created, the original" +msgstr "Canal de venta a través del cual se creó el folio, el original" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_line__sale_channel_id +msgid "Sale Channel through which reservation line was created" +msgstr "Canal de venta a través del cual se creó la línea de reserva" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__sale_channel_origin_id +msgid "Sale Channel through which reservation was created, the original" +msgstr "Canal de Venta a través del cual se creó la reserva, la original" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__sale_channel_origin_id +msgid "Sale Channel through which service was created, the original" +msgstr "Canal de venta a través del cual se creó el servicio, el original" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__sale_channel_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__sale_channel_ids +#: model:ir.ui.menu,name:pms.menu_open_pms_sale_channel_form_tree +msgid "Sale Channels" +msgstr "Canales de Venta" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__sale_channel_ids +msgid "Sale Channels through which reservation lines were managed" +msgstr "" +"Canales de venta a través de los cuales se gestionaban las líneas de reserva" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__sale_channel_ids +msgid "Sale Channels through which reservations were managed" +msgstr "Canales de venta a través de los cuales se gestionaron las reservas" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.product_pricelist_item_view_tree +msgid "Sale Date End" +msgstr "Fecha de finalización de la de venta" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.product_pricelist_item_view_tree +msgid "Sale Date Start" +msgstr "Fecha de inicio de la venta" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_massive_changes_wizard__date_types__sale_dates +msgid "Sale Dates" +msgstr "Fechas de venta" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room__description_sale +msgid "Sale Description" +msgstr "Descipción de la Venta" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Sale Details" +msgstr "Detalles de la Venta" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__sale_line_ids +#: model:ir.model.fields,field_description:pms.field_pms_service__sale_line_ids +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Sale Lines" +msgstr "Líneas de Venta" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__sale_order_count +msgid "Sale Order Count" +msgstr "Cuenta Pedido de Venta" + +#. module: pms +#: model:ir.model.fields,help:pms.field_product_pricelist__pms_sale_channel_ids +msgid "Sale channel for which the pricelist is included" +msgstr "Canal de venta para el que se incluye la tarifa" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__sale_line_ids +msgid "Sale lines" +msgstr "Líneas de Venta" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__sale_line_ids +msgid "Sale lines in folio. It correspond with reservation nights" +msgstr "Líneas de venta en la ficha. Corresponde con noches de reserva" + +#. module: pms +#: model:ir.ui.menu,name:pms.pms_sales_menu +msgid "Sales" +msgstr "Facturación" + +#. module: pms +#: model:ir.model,name:pms.model_pms_sale_channel +msgid "Sales Channel" +msgstr "Canales de Venta" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__channel_type_id +#: model:ir.model.fields,help:pms.field_pms_booking_engine__channel_type_id +msgid "Sales Channel through which the reservation was managed" +msgstr "Canal de venta a través del cual se gestionó la reserva" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__description_sale +msgid "Sales Description" +msgstr "Descripciones de la Venta" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__sale_line_ids +#: model:ir.model.fields,field_description:pms.field_pms_service_line__sale_line_ids +msgid "Sales Lines" +msgstr "Líneas de Ventas" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__sale_order_ids +msgid "Sales Order" +msgstr "Pedido de Venta" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__sale_line_warn +msgid "Sales Order Line" +msgstr "Línea de Pedido de Venta" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__list_price +#: model:ir.model.fields,field_description:pms.field_product_product__list_price +#: model:ir.model.fields,field_description:pms.field_product_template__list_price +msgid "Sales Price" +msgstr "Precio de Venta" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__team_id +msgid "Sales Team" +msgstr "Equipo de Venta" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__sale_warn +msgid "Sales Warnings" +msgstr "Advertencias de Venta" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service_line__date +msgid "Sate on which the product is to be consumed" +msgstr "Fecha de consumición del servicio" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.wizard_folio_changes_view_form +msgid "Saturday" +msgstr "Sábado" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Save" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.advanced_filters_wizard +msgid "Search" +msgstr "Buscar" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__lastname2 +msgid "Second Last Name" +msgstr "Segundo apellido" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Second Lastname (Optional)" +msgstr "Segundo Apellido (Opcional)" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Second Street (Optional)" +msgstr "Segunda Calle (Opcional)" + +#. module: pms +#: model:pms.ubication,name:pms.pms_ubication_2nd_floor +msgid "Second floor" +msgstr "Segundo piso" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__lastname2 +#: model:ir.model.fields,field_description:pms.field_res_partner__lastname2 +#: model:ir.model.fields,field_description:pms.field_res_users__lastname2 +msgid "Second last name" +msgstr "Segundo apellido" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__residence_street2 +#: model:ir.model.fields,field_description:pms.field_res_partner__residence_street2 +#: model:ir.model.fields,field_description:pms.field_res_users__residence_street2 +msgid "Second street of residence" +msgstr "Segunda calle de residencia" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__residence_street2 +#: model:ir.model.fields,help:pms.field_pms_property__residence_street2 +#: model:ir.model.fields,help:pms.field_res_partner__residence_street2 +#: model:ir.model.fields,help:pms.field_res_users__residence_street2 +msgid "Second street of the guest's residence" +msgstr "Segunda calle de la residencia del huésped" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__section_id +#: model:ir.model.fields.selection,name:pms.selection__folio_sale_line__display_type__line_section +msgid "Section" +msgstr "Sección" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__access_token +#: model:ir.model.fields,field_description:pms.field_pms_folio__access_token +#: model:ir.model.fields,field_description:pms.field_pms_reservation__access_token +msgid "Security Token" +msgstr "Señal de Seguridad" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_amenity__pms_amenity_type_id +msgid "Segment the amenities by categories (multimedia, comfort, etc ...)" +msgstr "" +"Segmenta las características por categorías (multimedia, confort, etc...)" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__segmentation_ids +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__segmentation_ids +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__segmentation_ids +#: model:ir.model.fields,field_description:pms.field_pms_folio__segmentation_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__segmentation_ids +msgid "Segmentation" +msgstr "Segmentación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__segmentation_ids +msgid "Segmentation tags to classify checkin partners" +msgstr "Etiquetas de segmentación para clasificar checkin partners" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__segmentation_ids +msgid "Segmentation tags to classify folios" +msgstr "Etiquetas de segmentación para clasificar fichas" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__segmentation_ids +msgid "Segmentation tags to classify reservations" +msgstr "Etiquetas de segmentación para clasificar reservas" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Segmentation..." +msgstr "Segmentación..." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__document_type +msgid "Select a valid document type" +msgstr "Seleccione un tipo de documento válido" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__categ_id +msgid "Select category for the current product" +msgstr "Seleccione una categoría para el producto actual" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.wizard_folio_changes_view_form +msgid "Selected reservations with different dates" +msgstr "Reservas seleccionadas con fechas diferentes" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__num_rooms_selected +msgid "Selected rooms" +msgstr "Habitaciones Seleccionadas" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__invoice_warn +#: model:ir.model.fields,help:pms.field_pms_property__picking_warn +#: model:ir.model.fields,help:pms.field_pms_property__sale_warn +#: model:ir.model.fields,help:pms.field_pms_room_type__sale_line_warn +msgid "" +"Selecting the \"Warning\" option will notify user with the message, " +"Selecting \"Blocking Message\" will throw an exception with the message and " +"block the flow. The Message has to be written in the next field." +msgstr "" +"La selección de la opción \"Advertencia\" notificará al usuario con el " +"mensaje, la Selección de \"Mensaje de Bloqueo\" lanzará una excepción con el " +"mensaje y bloqueará el flujo. El mensaje debe escribirse en el siguiente " +"campo." + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__self +msgid "Self" +msgstr "Si mismo" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Send Cancellation Email" +msgstr "Enviar Correo Electrónico de Cancelación" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Send Confirmation Email" +msgstr "Enviar Correo Electrónico de Confirmación" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Send Exit Email" +msgstr "Enviar Correo Electrónico de Salida" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folio_invitations +msgid "Send Invitation" +msgstr "Enviar Invitación" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "Send Mail " +msgstr "Enviar Correo " + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Send Modification Email" +msgstr "Enviar Correo Electrónico de Modificación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__sequence +#: model:ir.model.fields,field_description:pms.field_pms_folio__sequence +#: model:ir.model.fields,field_description:pms.field_pms_room__sequence +#: model:ir.model.fields,field_description:pms.field_pms_room_type__sequence +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class__sequence +#: model:ir.model.fields,field_description:pms.field_pms_service__sequence +#: model:ir.model.fields,field_description:pms.field_pms_team_member__sequence +#: model:ir.model.fields,field_description:pms.field_pms_ubication__sequence +msgid "Sequence" +msgstr "Secuencia" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__sequence +msgid "Sequence used to form the name of the folio" +msgstr "Secuencia utilizada para formar el nombre del folio" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Sequences" +msgstr "Secuencias" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__service_ids +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__service +#: model:ir.model.fields,field_description:pms.field_pms_service__product_id +#: model:ir.model.fields.selection,name:pms.selection__pms_massive_changes_wizard__apply_pricelists_on__service +msgid "Service" +msgstr "Servicio" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__service_order +msgid "Service Id" +msgstr "Id de Servicio" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_form +msgid "Service Line" +msgstr "Línea de servicio" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__service_line_ids +#: model:ir.model.fields,field_description:pms.field_pms_service__service_line_ids +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Service Lines" +msgstr "Líneas del servicio" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__service_id +msgid "Service Reference" +msgstr "Referencia del Servicio" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_service_line__service_id +msgid "Service Room" +msgstr "Habitación del Servicio" + +#. module: pms +#: model:ir.model,name:pms.model_pms_service_line +msgid "Service by day" +msgstr "Servicio por día" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_service__name +#: model:ir.model.fields,help:pms.field_pms_service__name +msgid "Service description" +msgstr "Descripción del servicio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service_line__service_id +msgid "Service identifier" +msgstr "Línea de servicio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__state +msgid "Service status, it corresponds with folio status" +msgstr "Estado del servicio, corresponde al estado de la ficha" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_pms_service_line_form +#: model:ir.actions.act_window,name:pms.action_pms_services_form +#: model:ir.model.fields,field_description:pms.field_account_bank_statement_line__service_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__service_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__service_ids +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__service_ids +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__service_ids +#: model:ir.ui.menu,name:pms.menu_services_pms +#: model:ir.ui.menu,name:pms.pms_services_menu +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_graph +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_pivot +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_calendar +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation_detail +msgid "Services" +msgstr "Servicios" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_service_line +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "Services By Day" +msgstr "Servicios por día" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "Services NOT included in the room reservation price" +msgstr "Servicios NO incluidos en el precio de reserva de la habitación" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__wizard_folio_changes__modification_type__services +msgid "Services Prices" +msgstr "Precios de los Servicios" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__price_services +msgid "Services Total" +msgstr "Servicios totales" + +#. module: pms +#: model:ir.model,name:pms.model_pms_service +msgid "Services and its charges" +msgstr "Servicios y sus cargos" + +#. module: pms +#: model:ir.ui.menu,name:pms.menu_pms_service_line +msgid "Services by Day" +msgstr "Servicios por día" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__service_ids +msgid "" +"Services detail provide to customer and it will include in main Invoice." +msgstr "" +"El detalle de los servicios se proporciona al cliente y se incluir´a en la " +"factura principal." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__services_discount +msgid "Services discount" +msgstr "Descuento Servicios" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__services_discount +msgid "Services discount (€)" +msgstr "Descuento Servicios (€)" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_bank_statement_line__service_ids +msgid "Services in which the Account Bank Statement Lines are included" +msgstr "" +"Servicios en los que se incluyen las Líneas de Extracto Bancario de Cuenta" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "Services included in the room reservation price" +msgstr "Servicios incluidos en el precio de la reserva de habitación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service__board_service_line_ids +#: model:ir.model.fields,help:pms.field_pms_board_service_room_type__board_service_line_ids +msgid "Services included in this Board Service" +msgstr "Servicios incluidos en el Régimen" + +#. module: pms +#: model:ir.model,name:pms.model_pms_board_service_line +msgid "Services on Board Service included" +msgstr "Servicios incluidos en el Régimen" + +#. module: pms +#: model:ir.model,name:pms.model_pms_board_service_room_type_line +msgid "Services on Board Service included in Room" +msgstr "Servicios del Régimen incluidos en la habitación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__overnight_room +#: model:ir.model.fields,help:pms.field_pms_reservation_line__overnight_room +#: model:ir.model.fields,help:pms.field_pms_room_type__overnight_room +#: model:ir.model.fields,help:pms.field_pms_room_type_class__overnight +msgid "Set False if if these types of spaces are not used for overnight stays" +msgstr "Ponga False si este tipo de espacios no se utilizan para pernoctar" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Set to Done" +msgstr "Pasar a hecho" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Settings" +msgstr "Ajustes" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__service_id +msgid "Sevice included in folio sale line" +msgstr "Servicio de Habitación incluído en la Habitación" + +#. module: pms +#: model:pms.amenity,name:pms.pms_amenity_shampoo_and_soap +msgid "Shampoo and Soap" +msgstr "Champú y Jabón" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__partner_share +msgid "Share Partner" +msgstr "Cliente compartido" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__shared_folio +msgid "Shared Folio" +msgstr "Ficha compartida" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_form +msgid "Shared Room" +msgstr "Habitación Compartida" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room__short_name +msgid "Short Name" +msgstr "Nombre Corto" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__pms_property_code +msgid "Short name property" +msgstr "Propiedad del nombre corto" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_board_service__show_detail_report +msgid "Show Detail Report" +msgstr "Ver Informe" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__show_on_hand_qty_status_button +msgid "Show On Hand Qty Status Button" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +msgid "Show all checkins for Tomorrow" +msgstr "Mostrar todos los checkin para mañana" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +msgid "Show all checkins for enter tomorrow" +msgstr "Mostrar todos los checkin que entrarán mañana" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +msgid "Show all future checkins" +msgstr "Mostrar todos los checkin para mañana" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Show all reservations for which date enter is before than 14 days" +msgstr "" +"Mostrar todas las reservas las cuales la fecha de entrada es anterior a 14 " +"días" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Show all reservations for which date enter is before than 7 days" +msgstr "" +"Mostrar todas las reservas las cuales la fecha de entrada es anterior a 7 " +"días" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "" +"Show all reservations for which date enter is before than aprox. 1 month" +msgstr "" +"Mostrar todas las reservas en las que la fecha de entrada es anterior a 1 " +"mes aprox" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service_line__auto_qty +msgid "Show if the day qty was calculated automatically" +msgstr "Mostrar si la cantidad diaria se ha calculado automáticamente" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__sign_on +msgid "Sign on" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__signature +msgid "Signature" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__signature +msgid "Signature of the guest" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__signup_expiration +msgid "Signup Expiration" +msgstr "Caducidad del Registro" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__signup_token +msgid "Signup Token" +msgstr "Señal del Registro" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__signup_type +msgid "Signup Token Type" +msgstr "Tipo de Señal del registro" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__signup_valid +msgid "Signup Token is Valid" +msgstr "La Señal del Registro es Válida" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__signup_url +msgid "Signup URL" +msgstr "URL de Inscripción" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__sii_enabled +msgid "Sii Enabled" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.report_invoice_document +msgid "Simplified Invoice" +msgstr "Factura Simplificada" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__journal_simplified_invoice_id +msgid "Simplified Invoice Journal" +msgstr "Diario de Facturas Simplificado" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_account_bank_statement_line__is_simplified_invoice +#: model:ir.model.fields,field_description:pms.field_account_journal__is_simplified_invoice +#: model:ir.model.fields,field_description:pms.field_account_move__is_simplified_invoice +#: model:ir.model.fields,field_description:pms.field_account_payment__is_simplified_invoice +msgid "Simplified invoice" +msgstr "Factura simplificada" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__sii_simplified_invoice +msgid "Simplified invoices in SII?" +msgstr "" + +#. module: pms +#: model:pms.room.type,name:pms.pms_room_type_single +#: model:product.product,name:pms.pms_room_type_single_product_product +msgid "Single" +msgstr "Individual" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__sales_count +msgid "Sold" +msgstr "Vendido" + +#. module: pms +#: code:addons/pms/models/res_users.py:0 +#, python-format +msgid "Some properties do not belong to the allowed companies" +msgstr "Algunas propiedades no pertenecen a las compañías permitidas" + +#. module: pms +#: model:ir.model.fields,help:pms.field_product_pricelist_item__board_service_room_type_id +msgid "Specify a Board services on Room Types." +msgstr "Especifica los regímenes disponibles en los tipos de habitación" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_cancelation_rule__apply_on_late__days +#: model:ir.model.fields.selection,name:pms.selection__pms_cancelation_rule__apply_on_noshow__days +msgid "Specify days" +msgstr "Días específicos" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation_split_join_swap_wizard__operation__split +#: model_terms:ir.ui.view,arch_db:pms.reservation_wizard +msgid "Split reservation" +msgstr "Dividir la reserva" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__splitted +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Splitted" +msgstr "Dividida" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_booking_duplicate__reservation_type__staff +#: model:ir.model.fields.selection,name:pms.selection__pms_booking_engine__reservation_type__staff +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__reservation_type__staff +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__reservation_type__staff +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Staff" +msgstr "Staff" + +#. module: pms +#: code:addons/pms/controllers/pms_portal.py:0 +#, python-format +msgid "Stage" +msgstr "Estado" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__cost_method +msgid "" +"Standard Price: The products are valued at their standard cost defined on " +"the product.\n" +" Average Cost (AVCO): The products are valued at weighted average " +"cost.\n" +" First In First Out (FIFO): The products are valued supposing those " +"that enter the company first will also leave it first.\n" +" " +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_product_pricelist_item__date_start_consumption +msgid "Start Date Consumption" +msgstr "Fecha de Inicio consumición" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_engine__start_date +msgid "Start date for creation of reservations and folios" +msgstr "Fecha de inicio para creación de reservas y fichas" + +#. module: pms +#: model:ir.model.fields,help:pms.field_product_pricelist_item__date_start_consumption +msgid "Start date to apply daily pricelist items" +msgstr "Fecha de inicio para aplicar los items de tarifa diarios" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__state_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__state +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__state +#: model:ir.model.fields,field_description:pms.field_pms_service__state +#: model:ir.model.fields,field_description:pms.field_res_partner__state_id +#: model:ir.model.fields,field_description:pms.field_res_users__state_id +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +#: model_terms:ir.ui.view,arch_db:pms.res_partner_view_form +msgid "State" +msgstr "Estado" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__invoice_status +msgid "" +"State in which the service is with respect to invoices.It can be 'invoiced', " +"'to_invoice' or 'no'" +msgstr "" +"Estado en el que se encuentra el servicio respecto a las facturas. Puede ser " +"'facturado', 'a facturar' o 'no'" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__residence_state_id +#: model:ir.model.fields,field_description:pms.field_pms_property__residence_state_id +#: model:ir.model.fields,field_description:pms.field_res_partner__residence_state_id +#: model:ir.model.fields,field_description:pms.field_res_users__residence_state_id +msgid "State of residence" +msgstr "Estado de residencia" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__residence_state_id +msgid "State of the guest's residence" +msgstr "Estado de residencia del huésped" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_line__state +msgid "State of the reservation line." +msgstr "Estado de la línea de reserva" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__statement_line_ids +msgid "Statement lines" +msgstr "líneas de extracto" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__state +#: model:ir.model.fields,field_description:pms.field_pms_folio__state +msgid "Status" +msgstr "Estado" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__activity_state +#: model:ir.model.fields,help:pms.field_pms_folio__activity_state +#: model:ir.model.fields,help:pms.field_pms_property__activity_state +#: model:ir.model.fields,help:pms.field_pms_reservation__activity_state +#: model:ir.model.fields,help:pms.field_pms_room_type__activity_state +msgid "" +"Status based on activities\n" +"Overdue: Due date is already passed\n" +"Today: Activity date is today\n" +"Planned: Future activities." +msgstr "" +"Estado basado en actividades\n" +"Vencido: la fecha de vencimiento ya pasó\n" +"Hoy: la fecha de actividad es hoy\n" +"Planificado: actividades futuras." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__state +msgid "Status of the checkin partner regarding the reservation" +msgstr "Ningún checkin fue realizado en esta reserva" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__stock_move_ids +msgid "Stock Move" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__picking_warn +msgid "Stock Picking" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__stock_quant_ids +msgid "Stock Quant" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__stock_valuation_layer_ids +msgid "Stock Valuation Layer" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__residence_street +#: model:ir.model.fields,field_description:pms.field_pms_property__street +#: model:ir.model.fields,field_description:pms.field_res_partner__street +#: model:ir.model.fields,field_description:pms.field_res_users__street +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Street" +msgstr "Calle" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +#: model_terms:ir.ui.view,arch_db:pms.res_partner_view_form +msgid "Street 2..." +msgstr "Calle 2..." + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__residence_street +#: model:ir.model.fields,field_description:pms.field_res_partner__residence_street +#: model:ir.model.fields,field_description:pms.field_res_users__residence_street +msgid "Street of residence" +msgstr "Calle de residencia" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__residence_street +#: model:ir.model.fields,help:pms.field_pms_property__residence_street +#: model:ir.model.fields,help:pms.field_res_partner__residence_street +#: model:ir.model.fields,help:pms.field_res_users__residence_street +msgid "Street of the guest's residence" +msgstr "Calle de la residencia del huésped" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +#: model_terms:ir.ui.view,arch_db:pms.res_partner_view_form +msgid "Street..." +msgstr "Calle" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__residence_street2 +#: model:ir.model.fields,field_description:pms.field_pms_property__street2 +#: model:ir.model.fields,field_description:pms.field_res_partner__street2 +#: model:ir.model.fields,field_description:pms.field_res_users__street2 +msgid "Street2" +msgstr "Calle2" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__service_line_ids +msgid "Subservices included in folio sale line service" +msgstr "Subservicios incluidos en el servicio de línea de venta de folios" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__service_line_ids +msgid "Subservices included in this service" +msgstr "Servicio de Habitación incluído en la Habitación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__price_subtotal +#: model:ir.model.fields,field_description:pms.field_pms_reservation__price_subtotal +#: model:ir.model.fields,field_description:pms.field_pms_service__price_subtotal +#: model:ir.model.fields,field_description:pms.field_pms_service_line__price_day_subtotal +msgid "Subtotal" +msgstr "Subtotal" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__price_subtotal +#: model:ir.model.fields,help:pms.field_pms_reservation__price_subtotal +#: model:ir.model.fields,help:pms.field_pms_service__price_subtotal +#: model:ir.model.fields,help:pms.field_pms_service_line__price_day_subtotal +msgid "Subtotal price without taxes" +msgstr "Subtotal sin impuestos" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.wizard_folio_changes_view_form +msgid "Sunday" +msgstr "Domingo" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__supplier_rank +msgid "Supplier Rank" +msgstr "Rango de los proveedores" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_amenity_view_form +msgid "Supplier Taxes" +msgstr "Impuesto a los proveedores" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_amenity_view_form +msgid "Suppliers" +msgstr "Proveedores" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.reservation_wizard +msgid "Swap reservation rooms" +msgstr "Intercambiar habitaciones en reservas" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation_split_join_swap_wizard__operation__swap +msgid "Swap rooms" +msgstr "Intercambiar habitaciones" + +#. module: pms +#. openerp-web +#: code:addons/pms/static/src/xml/pms_base_templates.xml:0 +#, python-format +msgid "Switch to this property" +msgstr "Cambiar a este Hotel" + +#. module: pms +#: model:ir.model,name:pms.model_ir_config_parameter +msgid "System Parameter" +msgstr "Parámetro del sistema" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "TRAVELER'S DOCUMENT" +msgstr "DOCUMENTO DEL VIAJERO" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +msgid "Tables Detail" +msgstr "Detalles de la tabla" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__category_id +msgid "Tags" +msgstr "Etiquetas" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__vat +#: model_terms:ir.ui.view,arch_db:pms.report_folio_document +msgid "Tax ID" +msgstr "ID del impuesto" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__tax_ids +#: model:ir.model.fields,field_description:pms.field_pms_folio__amount_tax +#: model:ir.model.fields,field_description:pms.field_pms_reservation__tax_ids +#: model:ir.model.fields,field_description:pms.field_pms_service__tax_ids +#: model:ir.model.fields,field_description:pms.field_pms_service_line__tax_ids +#: model_terms:ir.ui.view,arch_db:pms.report_folio_document +msgid "Taxes" +msgstr "Impuestos" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__price_tax +#: model:ir.model.fields,field_description:pms.field_pms_service__price_tax +#: model:ir.model.fields,field_description:pms.field_pms_service_line__price_day_tax +msgid "Taxes Amount" +msgstr "Cuenta de impuestos" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__tax_ids +msgid "Taxes applied in the folio sale line" +msgstr "Impuestos aplicados en el folio sale line" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__tax_ids +msgid "Taxes applied in the reservation" +msgstr "Impuestos aplicados en la reserva" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__tax_ids +msgid "Taxes applied in the service" +msgstr "Impuestos aplicados en el servicio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service_line__tax_ids +msgid "Taxes applied in the service line" +msgstr "Impuestos aplicados en la línea de servicio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_advance_payment_inv__deposit_taxes_id +msgid "Taxes used for deposits" +msgstr "Tasas utilizadas para depósitos" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__user_id +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Team Leader" +msgstr "Líder de Equipo" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__member_ids +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Team Members" +msgstr "Miembros del Equipo" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__folio_sequence +msgid "Techinal field to get reservation name" +msgstr "Campo técnico para obtener el nombre de la reserva" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_move_line__name_changed_by_user +msgid "" +"Techinal field to know if the name was set manually by the user\n" +" or by the system. If the name was set manually, the system will not\n" +" change it when the qty days are changed" +msgstr "" +"Campo técnico para saber si el nombre fue establecido manualmente por el " +"usuario\n" +" o por el sistema. Si el nombre fue puesto manualmente, el sistema no\n" +" lo cambiará cuando se modifiquen los días de cadencia" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__show_update_pricelist +msgid "" +"Technical Field, True if the pricelist was changed;\n" +" this will then display a recomputation button" +msgstr "" +"Campo técnico, verdadero si se ha modificado la lista de precios;\n" +" se mostrará un botón de recálculo" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__valid_product_template_attribute_line_ids +msgid "Technical compute" +msgstr "Computación técnica" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__document_id +msgid "Technical field" +msgstr "Computación técnica" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__display_type +msgid "Technical field for UX purpose." +msgstr "Campo técnico para fines de UX." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__payment_multi +msgid "Technical field for manage payments with multiple folios assigned" +msgstr "Campo técnico para gestionar pagos con múltiples folios asignados" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__preconfirm +msgid "Technical field that indicates the reservation is not comfirm yet" +msgstr "Campo técnico que indica que la reserva aún no está confirmada" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__no_auto_add_lines +msgid "" +"Technical field to avoid add service lines to service\n" +" automatically when creating a new service. It is used when\n" +" creating a new service with lines in vals\n" +" " +msgstr "" +"Campo técnico para evitar añadir líneas de servicio al servicio\n" +" automáticamente al crear un nuevo servicio. Se utiliza al\n" +" crear un nuevo servicio con líneas en vals\n" +" " + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__days_to_checkin +msgid "" +"Technical field to facilitate\n" +" filtering by dates related to checkin" +msgstr "" +"Campo técnico para facilitar\n" +" filtrar por fechas relacionadas con el registro de entrada" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__days_to_checkout +msgid "" +"Technical field to facilitate\n" +" filtering by dates related to checkout" +msgstr "" +"Campo técnico para facilitar\n" +" filtrar por fechas relacionadas con el proceso de salida" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_bank_statement_line__is_simplified_invoice +#: model:ir.model.fields,help:pms.field_account_move__is_simplified_invoice +#: model:ir.model.fields,help:pms.field_account_payment__is_simplified_invoice +msgid "Technical field to know if the invoice is simplified" +msgstr "Campo técnico para saber si la factura está simplificada" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__is_origin_channel_check_visible +msgid "Technical field to make visible update origin channel check" +msgstr "" +"Campo técnico para hacer visible la actualización de la comprobación del " +"canal de origen" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__allowed_checkin +msgid "" +"Technical field, Indicates if there isn't a checkin_partner dataOnly can be " +"true if checkin is today or was in the past" +msgstr "" +"Campo técnico, Indica si no hay datos de checkin_partner Solo puede ser " +"verdadero si el checkin es hoy o fue en el pasado" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__allowed_cancel +msgid "" +"Technical field, Indicates that reservation can be cancelled,that happened " +"when state is 'cancel', 'done', or 'departure_delayed'" +msgstr "" +"Campo técnico, indica que la reserva se puede cancelar, eso sucedió cuando " +"el estado es 'cancelar', 'hecho' o 'salida_retrasada'" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__allowed_checkout +msgid "" +"Technical field, Indicates that reservation is ready for checkoutonly can be " +"true if reservation state is 'onboard' or departure_delayedand checkout is " +"today or will be in the future" +msgstr "" +"Campo técnico, Indica que la reserva está lista para el pago. Sólo puede ser " +"verdadero si el estado de la reserva es \"a bordo\" o \"salida retrasada\" y " +"el pago es hoy o lo será en el futuro" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__pricelist_id +msgid "" +"Technical field. Used for searching on pricelists, not stored in database." +msgstr "" +"Campo técnico. Usado para buscar e las tarifas, no se almacena en la base de " +"datos." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__stock_move_ids +#: model:ir.model.fields,help:pms.field_pms_room_type__stock_quant_ids +msgid "Technical: used to compute quantities." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__note +msgid "Terms and conditions" +msgstr "Términos y condiciones" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_end +msgid "" +"Thank you!\n" +" Your check-in has been successful." +msgstr "" +"Gracias.\n" +" Su registro se ha realizado correctamente." + +#. module: pms +#: model:ir.model.fields,help:pms.field_res_partner_id_number__name +msgid "The ID itself. For example, Driver License number of this person" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "The Property and Sale Channel Origin are mandatory in the reservation" +msgstr "" +"El Origen de la Propiedad y el Canal de Venta son obligatorios en la reserva" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__vat +msgid "" +"The Tax Identification Number. Complete it if the contact is subjected to " +"government taxes. Used in some legal statements." +msgstr "" +"El número de identificación fiscal. Complételo si el contacto está sujeto a " +"impuestos gubernamentales. Utilizado en algunas declaraciones legales." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__administrative_user_id +msgid "The administrative manager in the folio" +msgstr "El gestor administrativo en el folio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_bank_statement_line__origin_agency_id +#: model:ir.model.fields,help:pms.field_account_move__origin_agency_id +#: model:ir.model.fields,help:pms.field_account_move_line__origin_agency_id +#: model:ir.model.fields,help:pms.field_account_payment__origin_agency_id +msgid "The agency where the folio account move originates" +msgstr "La agencia donde se origina el movimiento de la cuenta de folio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__origin_agency_id +msgid "The agency where the folio sale line originates" +msgstr "La agencia donde se origina la línea de venta del folio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__invoice_count +msgid "The amount of invoices in out invoice and out refund status" +msgstr "" +"El importe de las facturas en estado de fuera de factura y fuera de reembolso" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__pending_amount +msgid "The amount that remains to be paid" +msgstr "La cantidad que queda por pagar" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__folio_pending_amount +msgid "The amount that remains to be paid from folio" +msgstr "La cantidad que queda por pagar de la ficha" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__untaxed_amount_to_invoice +msgid "The amount to invoice" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__untaxed_amount_invoiced +msgid "The amount to invoice without taxes in the line of folio" +msgstr "La cantidad a facturar sin impuestos en la línea de la ficha" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__analytic_account_id +msgid "The analytic account related to a folio." +msgstr "La cuenta analítica relacionada con un folio." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan_rule__availability_plan_id +msgid "The availability plan that include the Availabilty Rule" +msgstr "El plan de disponibilidad que incluye la regla de disponibilidad" + +#. module: pms +#: code:addons/pms/models/pms_room.py:0 +#, python-format +msgid "" +"The capacity of the room must be greater than 0." +msgstr "La capacidad de la habitación debe ser mayor que 0." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__checkin_partner_ids +msgid "The checkin partners on a folio" +msgstr "Los clientes en un folio" + +#. module: pms +#: code:addons/pms/models/res_users.py:0 +#, python-format +msgid "The chosen property is not in the allowed properties for this user" +msgstr "" +"La propiedad escogida no se encuentra en las propiedades permitidas para " +"este usuario" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__closure_reason_id +msgid "The closure reason for a closure room" +msgstr "Motivo de cierre" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__company_id +msgid "The company for folio" +msgstr "La sociedad de la Ficha" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__company_id +msgid "The company in the folio sale line" +msgstr "La compañía en el folio sale line" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__company_id +msgid "The company that owns or operates this property." +msgstr "La compañía que posee u opera esta propiedad." + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__currency_id +msgid "The currency for the folio" +msgstr "La moneda de la ficha" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__currency_id +msgid "The currency of the property location" +msgstr "La moneda de la localización de la propiedad" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__currency_id +msgid "The currency used in relation to the folio" +msgstr "La cuenta analítica relacionada con un folio." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__currency_id +msgid "The currency used in relation to the pricelist" +msgstr "La moneda utilizada la tarifa." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service_line__currency_id +msgid "The currency used in relation to the service where it's included" +msgstr "La moneda utilizada en el servicio." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_line__date +msgid "The date of the reservation in reservation line" +msgstr "La fecha de la reserva en la línea de reserva" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__invoicing_month_day +#: model:ir.model.fields,help:pms.field_res_partner__invoicing_month_day +#: model:ir.model.fields,help:pms.field_res_users__invoicing_month_day +msgid "The day of the month to invoice" +msgstr "El día del mes a facturar" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__default_pricelist_id +msgid "The default pricelist used in this property." +msgstr "La tarifa por defecto usada en esta propiedad." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__fiscal_position_id +msgid "The fiscal position depends on the location of the client" +msgstr "" +"La posición fiscal determina las tasas/cuentas usadas para este contacto." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__property_account_position_id +msgid "" +"The fiscal position determines the taxes/accounts used for this contact." +msgstr "" +"La posición fiscal determina las tasas/cuentas usadas para este contacto." + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_advance_payment_inv__fixed_amount +msgid "The fixed amount to be invoiced in advance, taxes excluded." +msgstr "La cantidad fija será facturada por adelantado, tasas excluidas." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__partner_id +msgid "The folio customer" +msgstr "Cliente de la ficha" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_move_line__folio_line_ids +msgid "The folio lines in the account move lines" +msgstr "Las líneas de folio en la cuenta mueven líneas" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__folio_id +msgid "The folio where the reservations are included" +msgstr "La ficha donde se incluyen las reservas" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__untaxed_amount_to_invoice +msgid "The invoiced amount without taxes in the line of the folio" +msgstr "La cantidad facturada sin impuestos en la línea del folio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__invoicing_policy +#: model:ir.model.fields,help:pms.field_res_partner__invoicing_policy +#: model:ir.model.fields,help:pms.field_res_users__invoicing_policy +msgid "" +"The invoicing policy of the partner,\n" +" set Property to user the policy configured in the Property" +msgstr "" +"La política de facturación del socio,\n" +" establecer Propiedad para el usuario la política configurada en la Propiedad" + +#. module: pms +#: code:addons/pms/models/account_journal.py:0 +#, python-format +msgid "The journal %s is used for normal invoices in the properties: %s" +msgstr "" +"El diario %s se utiliza para las facturas normales en las propiedades: %s" + +#. module: pms +#: code:addons/pms/models/account_journal.py:0 +#, python-format +msgid "The journal %s is used for simplified invoices in the properties: %s" +msgstr "" +"El diario %s se utiliza para las facturas simplificadas en las propiedades: " +"%s" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__manager_user_id +msgid "The main manager in the folio" +msgstr "El principal gestor del folio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room__capacity +msgid "The maximum number of people that can occupy a room" +msgstr "El número máximo de personas que pueden ocupar una habitación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_team_member__pms_role +msgid "" +"The member role in the organizationIt can be 'Reception', 'Revenue', " +"'Administrative', or 'Manager'" +msgstr "" +"El rol del miembro en la organización Puede ser 'Recepción', 'Ingresos', " +"'Administrativo' o 'Gerente'" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_move_line__move_id +msgid "The move of this entry line." +msgstr "El movimiento de esta línea de entrada." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_sale_channel__name +msgid "The name of the sale channel" +msgstr "El nombre del canal de venta." + +#. module: pms +#: model:ir.model.fields,help:pms.field_room_closure_reason__name +msgid "The name that identifies the room closure reason" +msgstr "El nombre que identifica el motivo de cierre de la sala" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__pos_order_count +msgid "The number of point of sales orders related to this customer" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__total_rooms_count +msgid "The number of rooms in a room type" +msgstr "El número de habitaciones de un tipo de habitación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__count_rooms_pending_arrival +msgid "The number of rooms left to occupy." +msgstr "El número de habitaciones que quedan por ocupar." + +#. module: pms +#: code:addons/pms/models/folio_sale_line.py:0 +#, python-format +msgid "The ordered quantity has been updated." +msgstr "Se ha actualizado la cantidad pedida." + +#. module: pms +#: code:addons/pms/models/ir_config_parameter.py:0 +#, python-format +msgid "The parameter Advanced price rules cannot be modified" +msgstr "El parámetro de reglas de tarifa avanzadas no se puede modificar" + +#. module: pms +#: code:addons/pms/models/res_partner.py:0 +#, python-format +msgid "The partner %s cannot be deleted" +msgstr "El interlocutor %s no puede ser borrado" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__has_unreconciled_entries +msgid "" +"The partner has at least one unreconciled debit and credit since last time " +"the invoices & payments matching was performed." +msgstr "" +"El cliente tiene al menos un débito y un crédito no conciliados desde la " +"última vez que se realizó la conciliación de facturas y pagos." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__reference +msgid "The payment communication of this sale order." +msgstr "La comunicación de pago de esta orden de venta." + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_advance_payment_inv__amount +msgid "The percentage of amount to be invoiced in advance, taxes excluded." +msgstr "" +"El porcentaje de la cantidad a facturar por adelantado, tasas/impuestos " +"excluidos." + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "" +"The period to create this invoice is locked. Please contact your " +"administrator to unlock it." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_line__price +msgid "The price in a reservation line" +msgstr "El precio en la linea de reserva" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__amount_untaxed +msgid "The price without taxes on a folio" +msgstr "Precio sin impuestos" + +#. module: pms +#: code:addons/pms/wizards/folio_make_invoice_advance.py:0 +#, python-format +msgid "" +"The product used to invoice a down payment should\n" +" be of type 'Service'.\n" +" Please use another product or update this " +"product." +msgstr "" +"El producto usado para facrurar un anticipo debe\n" +"\t\tser del tipo 'Servicio'.\n" +"\t\tPor favor, use otro producto o actualice este producto." + +#. module: pms +#: code:addons/pms/wizards/folio_make_invoice_advance.py:0 +#, python-format +msgid "" +"The product used to invoice a down payment should\n" +" have an invoice policy set to \"Ordered " +"quantities\".\n" +" Please update your deposit product to be able\n" +" to create a deposit invoice." +msgstr "" +"El producto utilizado para facturar un anticipo debe\n" +" tener una política de facturación establecida en " +"\"Cantidades pequeñas\".\n" +" Por favor, actualice su producto de depósito(??) " +"para poder\n" +" crear una factura de depósito." + +#. module: pms +#: model:ir.model.fields,help:pms.field_res_users__pms_property_ids +msgid "The properties allowed for this user" +msgstr "" +"La propiedad escogida no se encuentra en las propiedades permitidas para " +"este usuario" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_bank_statement_line__pms_property_id +#: model:ir.model.fields,help:pms.field_account_move__pms_property_id +#: model:ir.model.fields,help:pms.field_account_payment__pms_property_id +msgid "The property associated to the account move" +msgstr "La propiedad asociada al movimiento de la cuenta" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__pms_property_id +msgid "The property for folios" +msgstr "La propiedad de la ficha" + +#. module: pms +#: model:ir.model.fields,help:pms.field_res_users__pms_property_id +msgid "The property that is selected within those allowed for the user" +msgstr "" +"El Hotel escogida no se encuentra en los hoteles permitidos para este usuario" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__qty_to_invoice +msgid "" +"The quantity to invoice. If the invoice policy is order, the quantity to " +"invoice is calculated from the ordered quantity. Otherwise, the quantity " +"delivered is used." +msgstr "" +"La cantidad a facturar. Si la política de facturación es pedido, la cantidad " +"a facturar se calcula a partir de la cantidad pedida. De lo contrario, se " +"utiliza la cantidad entregada." + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_not_checkin +msgid "" +"The quick registration system is not available for this reservation.
\n" +" If you have any questions, you can contact us:

Phone:" +msgstr "" +"El sistema de registro rápido no está disponible para esta reserva.
\n" +" Si tiene alguna duda, puede ponerse en contacto con nosotros:

Tel:" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__user_id +#: model:ir.model.fields,help:pms.field_pms_reservation__user_id +msgid "The reception manager in the folio" +msgstr "El jefe de recepción en el folio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_payment__origin_reference +msgid "The reference of the payment origin" +msgstr "La referencia del origen del pago" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "The reservation must be canceled by action: action_cancel" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "The reservation must be confirmed by action: action_confirm" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "The reservation type must be the same for all reservations in folio" +msgstr "El tipo de reserva debe ser el mismo para todas las reservas en folio" + +#. module: pms +#: code:addons/pms/models/pms_room.py:0 +#, python-format +msgid "" +"The reservation units are required on shared rooms." +msgstr "" +"Las unidades de reserva son obligatorias en las habitaciones compartidas." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__revenue_user_id +msgid "The revenue manager in the folio" +msgstr "El gestor de ingresos en el folio" + +#. module: pms +#: code:addons/pms/wizards/wizard_split_join_swap_reservation.py:0 +#, python-format +msgid "The room does not exist" +msgstr "La habitacion no existe" + +#. module: pms +#: code:addons/pms/wizards/wizard_split_join_swap_reservation.py:0 +#, python-format +msgid "The room is not available" +msgstr "La habitación no está disponible" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_line__room_id +msgid "The room of a reservation. " +msgstr "Habitación de una reserva" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__sale_channel_id +#: model:ir.model.fields,help:pms.field_res_partner__sale_channel_id +#: model:ir.model.fields,help:pms.field_res_users__sale_channel_id +msgid "The sale channel of the partner" +msgstr "El canal de venta del partner" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__lst_price +msgid "" +"The sale price is managed from the product template. Click on the 'Configure " +"Variants' button to set the extra attribute prices." +msgstr "" +"El precio de venta se gestiona desde la plantilla del producto. Haga click " +"en el botón 'Configurar variantes' para establecer los precios de los " +"atributos adicionales." + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__section_id +msgid "The section of the folio sale line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__folio_sequence_id +msgid "The sequence that formed the name of the folio." +msgstr "La secuencia que forma el nombre de la ficha." + +#. module: pms +#: code:addons/pms/models/pms_room.py:0 +#, python-format +msgid "The short name can't contain more than 4 characters" +msgstr "El nombre corto no puede contener más de 4 caracteres" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__payment_state +msgid "The state of the payment" +msgstr "Estado del pago" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__state +msgid "" +"The state of the reservation. It can be 'Pre-reservation', 'Pending " +"arrival', 'On Board', 'Out', 'Cancelled', 'Arrival Delayed' or 'Departure " +"Delayed'" +msgstr "" +"El estado de la reserva. Puede ser 'Pre-reserva', 'Pendiente de llegada', 'A " +"bordo', 'Fuera', 'Cancelada', 'Llegada retrasada' o 'Salida retrasada'" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__folio_payment_state +msgid "The status of the folio payment" +msgstr "El estado del pago de la ficha" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__state +msgid "The status of the folio related with folio sale line" +msgstr "El estado del folio relacionado con el folio sale line" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__invoice_status +msgid "" +"The status of the invoices in folio. Can be 'invoiced', 'to_invoice' or 'no'." +msgstr "" +"El estado de las facturas en folio. Puede ser 'facturado', 'a_factura' o " +"'no'." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__property_stock_customer +msgid "" +"The stock location used as destination when sending goods to this contact." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__property_stock_supplier +msgid "" +"The stock location used as source when receiving goods from this contact." +msgstr "" + +#. module: pms +#: code:addons/pms/models/account_move.py:0 +#, python-format +msgid "" +"The total amount of the simplified invoice is higher than the maximum amount " +"allowed for simplified invoices." +msgstr "" +"El importe total de la factura simplificada es superior al importe máximo " +"autorizado para las facturas simplificadas." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio_availability_wizard__price_total +#: model:ir.model.fields,help:pms.field_pms_reservation_duplicate__price_total +msgid "The total price in the folio" +msgstr "Precio total de la ficha" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__reservation_type +#: model:ir.model.fields,help:pms.field_pms_booking_engine__reservation_type +#: model:ir.model.fields,help:pms.field_pms_folio__reservation_type +msgid "" +"The type of the reservation. Can be 'Normal', 'Staff' or 'Out of Service'" +msgstr "Tipo de reserva. Puede ser 'Normal', 'Personal' o 'Fuera de servicio'" + +#. module: pms +#: code:addons/pms/wizards/folio_make_invoice_advance.py:0 +#, python-format +msgid "The value of the down payment amount must be positive." +msgstr "El valor del pago inicial debe ser positivo." + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folio +msgid "There are currently no folios for your account." +msgstr "Actualmente no hay fichas en su cuenta." + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation +msgid "There are currently no reservations for your account." +msgstr "Actualmente no hay reservas en su cuenta." + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folio_invitations +msgid "There are currently no reservations in this folio for your account." +msgstr "Actualmente no hay reservas en este folio para su cuenta." + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "There are no checkins to print" +msgstr "No hay checkins para imprimir" + +#. module: pms +#: code:addons/pms/models/pms_reservation_line.py:0 +#, python-format +msgid "There is no availability for the room type %s on %s" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "" +"There is nothing to invoice!\n" +"\n" +" Reason(s) of this behavior could be:\n" +" - You should deliver your products before invoicing them: Click on " +"the \"truck\"\n" +" icon (top-right of your screen) and follow instructions.\n" +" - You should modify the invoicing policy of your product: Open the " +"product,\n" +" go to the \"Sales tab\" and modify invoicing policy from \"delivered " +"quantities\"\n" +" to \"ordered quantities\".\n" +" " +msgstr "" +"¡No hay nada para facturar!\n" +"\n" +" La/s razón/es de este comportamiento pueden ser:\n" +" - Debe entregar sus productos antes de facturarlos: haga click en el " +"icono del\n" +" \"???\" (arriba a la derecha de su pantalla) y siga las " +"instrucciones.\n" +" - Debe modificar la política de privacidad de su producto: Abre el " +"producto,\n" +" vaya a la pestaña \"Ventas\"y modifique la política de facturación " +"de \"cantidades entregadas\"\n" +" a \"cantidades pedidas\".\n" +" " + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_folio_error +msgid "There was an error processing this page." +msgstr "Se ha producido un error al procesar esta página." + +#. module: pms +#: code:addons/pms/wizards/wizard_split_join_swap_reservation.py:0 +#, python-format +msgid "There's no reservations lines with provided room" +msgstr "No hay líneas de reservas enla habitación proporcionada." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability__reservation_line_ids +#: model:ir.model.fields,help:pms.field_pms_reservation__reservation_line_ids +msgid "" +"They are the lines of the reservation into a reservation,they corresponds to " +"the nights" +msgstr "Son las lineas de la reserva en una reserva, corresponden a las noches" + +#. module: pms +#: model:pms.ubication,name:pms.pms_ubication_3rd_floor +msgid "Third floor" +msgstr "Tercer piso" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__property_account_payable_id +msgid "" +"This account will be used instead of the default one as the payable account " +"for the current partner" +msgstr "" +"Esta cuenta se usará en vez de la predeterminada como la cuenta para pagar " +"del cliente actual" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__property_account_receivable_id +msgid "" +"This account will be used instead of the default one as the receivable " +"account for the current partner" +msgstr "" +"Esta cuenta se usará en vez de la predeterminada como la cuenta para cobrar " +"del cliente actual" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__force_update_origin +msgid "" +"This field is for force update in sale channel origin of folio and another " +"reservations" +msgstr "" +"Este campo es para forzar actualización en canal de venta origen de folio y " +"otro reservas" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__checkin_datetime +msgid "" +"This field is the day and time of arrival of the reservation.It is formed " +"with the checkin and arrival_hour fields" +msgstr "" +"Este campo es el día y hora de llegada de la reserva. Se forma con los " +"campos checkin y arrival_hour" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__checkout_datetime +msgid "" +"This field is the day and time of departure of the reservation.It is formed " +"with the checkout and departure_hour fields" +msgstr "" +"Este campo es el día y hora de salida de la reserva. Se forma con los campos " +"checkout y dispatch_hour" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__tz +msgid "This field is used to determine de timezone of the property." +msgstr "Este campo se utiliza para determinar la zona horaria de la propiedad." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__email_normalized +msgid "" +"This field is used to search on email address as the primary email field can " +"contain more than strictly an email address." +msgstr "" +"Este campo se usa para buscar en la dirección de correo electrónico, ya que " +"el campo de correo electrónico principal puede contener más que " +"estrictamente una dirección de correo electrónico." + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "" +"This folio has payments assigned to multiple folios (through an invoice or " +"directly).\n" +" The Folio will only be considered paid if all the folios " +"with the same associated\n" +" payment are paid, or if a specific payment is assigned " +"to this folio." +msgstr "" +"Este folio tiene pagos asignados a varios folios (a través de una factura o " +"directamente).\n" +" El Folio sólo se considerará pagado si todos los folios con el mismo\n" +" o si se asigna un pago específico a este folio." + +#. module: pms +#: code:addons/pms/models/pms_checkin_partner.py:0 +#, python-format +msgid "This guest is already registered in the room" +msgstr "El invitado ya está registrado en la habitación" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__price_extra +msgid "This is the sum of the extra price of all attributes" +msgstr "Esta es la suma del precio extra de todos los atributos" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__property_supplier_payment_term_id +msgid "" +"This payment term will be used instead of the default one for purchase " +"orders and vendor bills" +msgstr "" +"Este término de pago será utilizado en lugar del predeterminado para las " +"órdenes de compra y facturas de proveedores" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__property_payment_term_id +msgid "" +"This payment term will be used instead of the default one for sales orders " +"and customer invoices" +msgstr "" +"Este término de pago será utilizado en lugar del predeterminado para los " +"pedido de venta y las facturas de los clientes" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__property_product_pricelist +msgid "" +"This pricelist will be used, instead of the default one, for sales to the " +"current partner" +msgstr "" +"Esta tarifa se utilizará, en lugar de la tarifa por defecto, para las " +"ventas al cliente actual" + +#. module: pms +#: code:addons/pms/models/folio_sale_line.py:0 +#, python-format +msgid "" +"This quantity was already invoiced. You must reduce the invoiced quantity " +"first." +msgstr "" +"Esta cantidad ya ha sido facturada. Primero debe reducir la cantidad " +"facturada." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_line__occupies_availability +msgid "This record is taken into account to calculate availability" +msgstr "Este registro se tiene en cuenta para calcular la disponibilidad" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "This reservation cannot be cancelled" +msgstr "No se puede hacer el checkout de la reserva" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "This reservation cannot be check out" +msgstr "No se puede hacer el checkout de la reserva" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "" +"This reservation has other reservantions and/or services in the\n" +" folio, you can check it in the" +msgstr "" +"Esta reserva tiene otras reservas y/o servicios en el\n" +"\t\tfolio, puedes comprobarlo/consultarlo en el" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "" +"This reservation is part of a splitted reservation, you can try to\n" +" join the reservation here" +msgstr "" +"Esta reserva forma parte de una reserva partida, puede intentar\n" +"\t\tunificar la reserva aquí" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__property_stock_production +msgid "" +"This stock location will be used, instead of the default one, as the source " +"location for stock moves generated by manufacturing orders." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__property_stock_inventory +msgid "" +"This stock location will be used, instead of the default one, as the source " +"location for stock moves generated when you do an inventory." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__responsible_id +msgid "" +"This user will be responsible of the next activities related to logistic " +"operations for this product." +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "This will update all unit prices based on the currently set pricelist." +msgstr "" +"Esto actualizará todos los precios unitarios según la lista de precios " +"establecida actualmente." + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.wizard_folio_changes_view_form +msgid "Thursday" +msgstr "Jueves" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__tz +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Timezone" +msgstr "Zona Horaria" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__tz_offset +msgid "Timezone offset" +msgstr "Desplazamiento de zona horaria" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__title +msgid "Title" +msgstr "Título" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__end_date +msgid "To" +msgstr "Hasta:" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__to_assign +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "To Assign" +msgstr "Por Asignar" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__invoice_status__to_confirm +msgid "To Confirm" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__folio_sale_line__invoice_status__to_invoice +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__invoice_status__to_invoice +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__invoice_status__to_invoice +#: model:ir.model.fields.selection,name:pms.selection__pms_service__invoice_status__to_invoice +msgid "To Invoice" +msgstr "Por Facturar" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__qty_to_invoice +msgid "To Invoice Quantity" +msgstr "Cantidad a facturar" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__to_send_cancelation_mail +msgid "To Send Cancelation Mail" +msgstr "Enviar Correo de Cancelación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__to_send_confirmation_mail +msgid "To Send Confirmation Mail" +msgstr "Enviar Correo de Confirmación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__to_send_exit_mail +msgid "To Send Exit Mail" +msgstr "Para Enviar Correo de Salida" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__to_send_modification_mail +msgid "To Send Modification Mail" +msgstr "Para Enviar Correo de Modificación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__to_weight +msgid "To Weigh With Scale" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "To be paid" +msgstr "Por Pagar" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "To enter" +msgstr "Por entrar" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +msgid "To invoice" +msgstr "Por Facturar" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__partner_name +msgid "To whom the room is assigned" +msgstr "A quién está asignada la habitación" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__end_date +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__checkout +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__checkout +msgid "To:" +msgstr "Hasta:" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "Today" +msgstr "Hoy" + +#. module: pms +#: model:pms.amenity.type,name:pms.pms_amenity_type_toiletries +msgid "Toiletries" +msgstr "Artículos de aseo" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "Tomorrow" +msgstr "Mañana" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__price_total +#: model:ir.model.fields,field_description:pms.field_pms_folio__amount_total +#: model:ir.model.fields,field_description:pms.field_pms_reservation__price_total +#: model:ir.model.fields,field_description:pms.field_pms_service__price_total +#: model:ir.model.fields,field_description:pms.field_pms_service_line__price_day_total +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_tree +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folio +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation +msgid "Total" +msgstr "Total" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__total_invoiced +msgid "Total Invoiced" +msgstr "Total facturado" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__debit +msgid "Total Payable" +msgstr "Total a pagar" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_tree +msgid "Total Pending" +msgstr "Total Pendiente" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__total_price_folio +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__total_price_folio +msgid "Total Price" +msgstr "Precio Total" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__credit +msgid "Total Receivable" +msgstr "Total por cobrar" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__total_rooms_count +msgid "Total Rooms Count" +msgstr "Nº Total de habitaciones" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__price_tax +msgid "Total Tax" +msgstr "Impuestos totales" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_tree +msgid "Total amount" +msgstr "Importe total" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "" +"Total amount\n" +" (Reservation Card):" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "Total amount (Reservation Card):" +msgstr "Importe total (Tarjeta de Reserva):" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__credit +msgid "Total amount this customer owes you." +msgstr "Cantidad total que este cliente le debe." + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__amount_total +msgid "Total amount to be paid" +msgstr "Cantidad total a pagar" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__debit +msgid "Total amount you have to pay to this vendor." +msgstr "Cantidad total que debe pagar al proveedor." + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__price_tax +#: model:ir.model.fields,help:pms.field_pms_reservation__price_tax +msgid "Total of taxes in a reservation" +msgstr "Unificar la reserva" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__price_tax +msgid "Total of taxes in service" +msgstr "Causa de estar Fuera de Servicio" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__price_total +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__price_total +msgid "Total price" +msgstr "Precio Total" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__price_services +msgid "Total price from services of a reservation" +msgstr "Precio total de los servicios de una reserva" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__total_price_folio +#: model:ir.model.fields,help:pms.field_pms_booking_engine__total_price_folio +msgid "Total price of folio with taxes" +msgstr "Precio total de la ficha con impuestos" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__price_room_services_set +msgid "Total price of room and services" +msgstr "Precio total de la habitación y servicios" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__price_total +#: model:ir.model.fields,help:pms.field_pms_reservation__price_total +#: model:ir.model.fields,help:pms.field_pms_service__price_total +#: model:ir.model.fields,help:pms.field_pms_service_line__price_day_total +msgid "Total price with taxes" +msgstr "Precio Total" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__service_type +msgid "Track Service" +msgstr "Servicio de Seguimiento" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__tracking +msgid "Tracking" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__transaction_ids +msgid "Transactions" +msgstr "Transacciones" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "Traveler's signature" +msgstr "Firma del viajero" + +#. module: pms +#: model:ir.actions.report,name:pms.action_traveller_report +msgid "Traveller Report" +msgstr "Informes de huéspedes" + +#. module: pms +#: model:pms.room.type,name:pms.pms_room_type_triple +#: model:product.product,name:pms.pms_room_type_triple_product_product +msgid "Triple" +msgstr "Triple" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_amenity__is_add_code_room_name +msgid "" +"True if the Internal Reference should appear in the display name of the rooms" +msgstr "" +"True si la referencia interna debe aparecer en el nombre para mostrar de las " +"habitaciones" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service__show_detail_report +msgid "True if you want that board service detail to be shown on the report" +msgstr "" +"Verdadero si desea que los detalles del servicio de la junta se muestren en " +"el informe" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.wizard_folio_changes_view_form +msgid "Tuesday" +msgstr "Martes" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__reservation_type +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__reservation_type +#: model:ir.model.fields,field_description:pms.field_pms_folio__reservation_type +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "Type" +msgstr "TIpo" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__reservation_type +msgid "Type of reservations. It can be 'normal', 'staff' or 'out of service" +msgstr "" +"Tipo de reservas. Puede ser \"normal\", \"personal\" o \"fuera de servicio\"" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_sale_channel__channel_type +msgid "" +"Type of sale channel; it can be 'direct'(if there isno intermediary) or " +"'indirect'(if there areintermediaries between partner and property" +msgstr "" +"Tipo de canal de venta; puede ser \"directo\" (si no hay intermediario) o " +"\"indirecto\" (si hay intermediarios entre el socio y la propiedad)" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__activity_exception_decoration +#: model:ir.model.fields,help:pms.field_pms_folio__activity_exception_decoration +#: model:ir.model.fields,help:pms.field_pms_property__activity_exception_decoration +#: model:ir.model.fields,help:pms.field_pms_reservation__activity_exception_decoration +#: model:ir.model.fields,help:pms.field_pms_room_type__activity_exception_decoration +msgid "Type of the exception activity on record." +msgstr "Tipo de actividad de excepcion/excepcional regsitrada./en un registro." + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Type your country here" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Type your nationality here" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Type your state here" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Type your zip code here" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class__room_type_ids +msgid "Types" +msgstr "Tipos" + +#. module: pms +#: model:ir.model,name:pms.model_pms_ubication +#: model:ir.model.fields,field_description:pms.field_pms_room__ubication_id +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_search +msgid "Ubication" +msgstr "Zona" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_ubication__name +#: model:ir.model.fields,help:pms.field_pms_ubication__name +msgid "Ubication Name" +msgstr "Nombre de la Zona" + +#. module: pms +#: model:ir.actions.act_window,name:pms.open_pms_ubication_form_tree +msgid "Ubication Structure" +msgstr "Zonas del Hotel" + +#. module: pms +#: model:ir.ui.menu,name:pms.menu_open_pms_ubication_form_tree +msgid "Ubications" +msgstr "Zonas" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service__default_code +msgid "Unique Board Service identification code per property" +msgstr "Código de identificación único del board service por property" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room__room_type_id +msgid "Unique room type for the rooms" +msgstr "Tipo de habitación único para las habitaciones." + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__price_unit +#: model:ir.model.fields,field_description:pms.field_pms_service_line__price_unit +#: model_terms:ir.ui.view,arch_db:pms.report_folio_document +msgid "Unit Price" +msgstr "Precio unidad" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__price_unit +msgid "Unit Price of folio sale line" +msgstr "Precio unitario del folio sale line" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__per_day +#: model:ir.model.fields,field_description:pms.field_product_product__per_day +#: model:ir.model.fields,field_description:pms.field_product_template__per_day +msgid "Unit increment per day" +msgstr "Unidad por unidad por día" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__per_person +#: model:ir.model.fields,field_description:pms.field_product_product__per_person +#: model:ir.model.fields,field_description:pms.field_product_template__per_person +msgid "Unit increment per person" +msgstr "Unidad por persona" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__product_uom +#: model:ir.model.fields,field_description:pms.field_pms_room_type__uom_id +msgid "Unit of Measure" +msgstr "Unidad de medida" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__product_uom_category_id +msgid "Unit of Measure Category" +msgstr "Nombre de unidad de medida" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__uom_name +msgid "Unit of Measure Name" +msgstr "Nombre de unidad de medida" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_service_line__day_qty +#: model:pms.room.type,uom_name:pms.demo_pms_room_type_grand_suite +#: model:pms.room.type,uom_name:pms.demo_pms_room_type_junior_suite +#: model:pms.room.type,uom_name:pms.pms_room_type_conference_room +#: model:pms.room.type,uom_name:pms.pms_room_type_double +#: model:pms.room.type,uom_name:pms.pms_room_type_economic +#: model:pms.room.type,uom_name:pms.pms_room_type_parking +#: model:pms.room.type,uom_name:pms.pms_room_type_quadruple +#: model:pms.room.type,uom_name:pms.pms_room_type_single +#: model:pms.room.type,uom_name:pms.pms_room_type_triple +#: model:product.product,uom_name:pms.demo_pms_room_type_grand_suite_product_product +#: model:product.product,uom_name:pms.demo_pms_room_type_junior_suite_product_product +#: model:product.product,uom_name:pms.pms_room_type_conference_room_product_product +#: model:product.product,uom_name:pms.pms_room_type_double_product_product +#: model:product.product,uom_name:pms.pms_room_type_economic_product_product +#: model:product.product,uom_name:pms.pms_room_type_parking_product_product +#: model:product.product,uom_name:pms.pms_room_type_quadruple_product_product +#: model:product.product,uom_name:pms.pms_room_type_single_product_product +#: model:product.product,uom_name:pms.pms_room_type_triple_product_product +#: model:product.product,uom_name:pms.pms_service_breakfast_buffet +#: model:product.product,uom_name:pms.pms_service_dinner +#: model:product.product,uom_name:pms.pms_service_extra_bed +#: model:product.product,uom_name:pms.pms_service_free_bar +#: model:product.product,uom_name:pms.pms_service_late_checkout +#: model:product.product,uom_name:pms.pms_service_lunch +#: model:product.template,uom_name:pms.pms_service_breakfast_buffet_product_template +#: model:product.template,uom_name:pms.pms_service_dinner_product_template +#: model:product.template,uom_name:pms.pms_service_extra_bed_product_template +#: model:product.template,uom_name:pms.pms_service_free_bar_product_template +#: model:product.template,uom_name:pms.pms_service_late_checkout_product_template +#: model:product.template,uom_name:pms.pms_service_lunch_product_template +msgid "Units" +msgstr "Unidades" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_checkin_partner__state__dummy +msgid "Unkown Guest" +msgstr "Huésped desconocido" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__message_unread +#: model:ir.model.fields,field_description:pms.field_pms_folio__message_unread +#: model:ir.model.fields,field_description:pms.field_pms_property__message_unread +#: model:ir.model.fields,field_description:pms.field_pms_reservation__message_unread +#: model:ir.model.fields,field_description:pms.field_pms_room_type__message_unread +msgid "Unread Messages" +msgstr "Mensajes no leídos" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__message_unread_counter +#: model:ir.model.fields,field_description:pms.field_pms_folio__message_unread_counter +#: model:ir.model.fields,field_description:pms.field_pms_property__message_unread_counter +#: model:ir.model.fields,field_description:pms.field_pms_reservation__message_unread_counter +#: model:ir.model.fields,field_description:pms.field_pms_room_type__message_unread_counter +msgid "Unread Messages Counter" +msgstr "Nº de mensajes sin leer" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "Unsupported operator %s for searching on date" +msgstr "Operador %s no soportado para búsquedas en fechas" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__amount_untaxed +msgid "Untaxed Amount" +msgstr "Importe no tributable" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__untaxed_amount_to_invoice +msgid "Untaxed Amount To Invoice" +msgstr "Importe no tributable a facturar" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__untaxed_amount_invoiced +msgid "Untaxed Invoiced Amount" +msgstr "Importe facturado no tributable" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "UoM" +msgstr "UdM (Unidad De Medida)" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Update Prices" +msgstr "Precio Único" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__force_update_origin +msgid "Update Sale Channel Origin" +msgstr "Actualización Canal Venta Origen" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_res_company__url_advert +msgid "Url Advert" +msgstr "Url Anuncio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_res_company__url_advert +msgid "Url to identify the ad" +msgstr "Url para identificar el anuncio" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__barcode +msgid "Use a barcode to identify this contact." +msgstr "Use un código de barras para identificar a este contacto." + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__overnight_room +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__overnight_room +#: model:ir.model.fields,field_description:pms.field_pms_room_type__overnight_room +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class__overnight +msgid "Use for overnight stays" +msgstr "Uso para estancias nocturnas" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_journal__allowed_pms_payments +msgid "Use to pay for reservations" +msgstr "Utilizar para pagar reservas" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_journal__is_simplified_invoice +msgid "Use to simplified invoice" +msgstr "Utilizar para simplificar la factura" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__used_room_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__used_room_ids +msgid "Used Rooms" +msgstr "Habitaciones Usadas" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_res_partner_category__is_used_in_checkin +msgid "Used in checkin" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__shared_folio +msgid "Used to notify is the reservation folio has other reservations/services" +msgstr "" +"Se utiliza para notificar si la ficha de reservas tiene otras reservas/" +"servicios" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__aeat_identification_type +msgid "" +"Used to specify an identification type to send to SII. Normally for sending " +"national and export invoices to SII where the customer country is not Spain, " +"it would calculate an identification type of 04 if the VAT field is filled " +"and 06 if it was not. This field is to specify types of 03 through 05, in " +"the event that the customer doesn't identify with a foreign VAT and instead " +"with their passport or residential certificate. If there is no value it will " +"work as before." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_team_member__user_id +msgid "User Member" +msgstr "Miembro Usuario" + +#. module: pms +#: model:ir.model,name:pms.model_res_users +msgid "Users" +msgstr "Usuarios" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__currency_id +msgid "Utility field to express amount currency" +msgstr "Campo de utilidad para expresar la cantidad de moneda" + +#. module: pms +#: model:room.closure.reason,name:pms.pms_room_closure_reason_vip_privacy +msgid "VIP Privacy" +msgstr "Privacidad VIP" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__valid_product_template_attribute_line_ids +msgid "Valid Product Attribute Lines" +msgstr "Líneas de atributo de producto válidas" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_res_partner_id_number__valid_from +msgid "Valid from" +msgstr "Válido desde" + +#. module: pms +#: model:ir.model.fields,help:pms.field_res_partner_id_number__valid_from +msgid "Validation period stating date." +msgstr "Fecha de inicio del período de validación." + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_num_rooms_selection__value +msgid "Value" +msgstr "Valor" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__value_svl +msgid "Value Svl" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__image_variant_1920 +msgid "Variant Image" +msgstr "Imagen de Variante" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__image_variant_1024 +msgid "Variant Image 1024" +msgstr "Imagen de Variante 1024" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__image_variant_128 +msgid "Variant Image 128" +msgstr "Imagen de Variante128" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__image_variant_256 +msgid "Variant Image 256" +msgstr "Imagen de Variante 256" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__image_variant_512 +msgid "Variant Image 512" +msgstr "Imagen de Variante 512" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__price_extra +msgid "Variant Price Extra" +msgstr "Precio de variante extra" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__variant_seller_ids +msgid "Variant Seller" +msgstr "Vendedor de variantes" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.report_invoice_document +msgid "Vendor Bill" +msgstr "Factura de Vendedor" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.report_invoice_document +msgid "Vendor Credit Note" +msgstr "Nota de Crédito de Vendedor" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__property_stock_supplier +msgid "Vendor Location" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__property_supplier_payment_term_id +msgid "Vendor Payment Terms" +msgstr "Términos/Condiciones de pago del proveedor" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__supplier_taxes_id +msgid "Vendor Taxes" +msgstr "Tasas del proveedor" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__seller_ids +msgid "Vendors" +msgstr "Proveedores" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "View Customer" +msgstr "Ver cliente" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.booking_duplicate +msgid "View Folios" +msgstr "Ver Folios" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__volume +msgid "Volume" +msgstr "Volumen" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__volume_uom_name +msgid "Volume unit of measure label" +msgstr "Etiqueta de unidad de medida de volumen" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__warehouse_id +msgid "Warehouse" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_service.py:0 +#, python-format +msgid "Warning for %s" +msgstr "Advertencia para %s" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__cardex_warning +msgid "Warning in Cardex" +msgstr "Advertencia en Cardex" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__website +msgid "Website Link" +msgstr "Enlace del sitio web" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__website_message_ids +#: model:ir.model.fields,field_description:pms.field_pms_folio__website_message_ids +#: model:ir.model.fields,field_description:pms.field_pms_property__website_message_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__website_message_ids +#: model:ir.model.fields,field_description:pms.field_pms_room_type__website_message_ids +msgid "Website Messages" +msgstr "Mensajes del sitio web" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__website_message_ids +#: model:ir.model.fields,help:pms.field_pms_folio__website_message_ids +#: model:ir.model.fields,help:pms.field_pms_property__website_message_ids +#: model:ir.model.fields,help:pms.field_pms_reservation__website_message_ids +#: model:ir.model.fields,help:pms.field_pms_room_type__website_message_ids +msgid "Website communication history" +msgstr "Historial de comunicación del sitio web" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.wizard_folio_changes_view_form +msgid "Wednesday" +msgstr "Miércoles" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__weight +msgid "Weight" +msgstr "Peso" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__weight_uom_name +msgid "Weight unit of measure label" +msgstr "Etiqueta de unidad de medida de peso" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__force_nothing_to_invoice +msgid "" +"When you set this field, the folio will be considered as nothin to invoice, " +"even when there may be ordered quantities pending to invoice." +msgstr "" +"Al configurar este campo, el folio será considerado como no facturado, aún " +"cuando existan cantidades ordenadas pendientes de facturar." + +#. module: pms +#: model:pms.amenity,name:pms.pms_amenity_wi_fi +msgid "Wi-Fi" +msgstr "Wi-Fi" + +#. module: pms +#: model:ir.model,name:pms.model_pms_advanced_filters_wizard +msgid "Wizard for advanced filters" +msgstr "Asistente de filtros avanzados" + +#. module: pms +#: model:ir.model,name:pms.model_pms_massive_changes_wizard +msgid "Wizard for massive changes on Availability Plans & Pricelists." +msgstr "" +"Asistente de cambios masivos en los Planes de Disponibilidad y las Tarifas." + +#. module: pms +#: code:addons/pms/wizards/pms_booking_duplicate.py:0 +#, python-format +msgid "" +"You can not create a new folio because there are rooms already occupied.\n" +" Please, check the rooms marked in red and try again." +msgstr "" +"No puede crear un nuevo folio porque ya hay habitaciones ocupadas.\n" +" Por favor, compruebe las habitaciones marcadas en rojo e inténtelo de nuevo." + +#. module: pms +#: code:addons/pms/models/folio_sale_line.py:0 +#, python-format +msgid "" +"You cannot delete a sale order line once a invoice has been created from it." +msgstr "" + +#. module: pms +#: model:ir.model.constraint,message:pms.constraint_pms_room_room_property_unique +msgid "" +"You cannot have more than one room with the same name in the same property" +msgstr "" +"No puede haber más de una habitación con el mismo nombre en el mismo inmueble" + +#. module: pms +#: model:ir.model.constraint,message:pms.constraint_pms_room_room_short_name_unique +msgid "" +"You cannot have more than one room with the same short name in the same " +"property" +msgstr "" +"No puede tener más de una habitación con el mismo nombre corto en la misma " +"propiedad" + +#. module: pms +#: code:addons/pms/models/folio_sale_line.py:0 +#, python-format +msgid "" +"You cannot reduce the invoiced quantity below\n" +" the quantity already invoiced." +msgstr "" + +#. module: pms +#: code:addons/pms/models/account_move.py:0 +#, python-format +msgid "" +"You cannot validate this invoice. Please check the partner has the complete " +"information required." +msgstr "" +"No puede validar esta factura. Por favor, compruebe que el socio " +"tiene la información completa requerida." + +#. module: pms +#: code:addons/pms/wizards/wizard_advanced_filters.py:0 +#, python-format +msgid "You must add filters to perform the search" +msgstr "Debe añadir filtros para realizar la búsqueda" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "You must assign a customer name" +msgstr "Debes asignar un nombre a la reserva" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "" +"You must select a cancelation template in the email configuration menu of " +"the property" +msgstr "" +"Debe seleccionar una plantilla de cancelación en el menú de configuración de " +"correo electrónico de la propiedad" + +#. module: pms +#: code:addons/pms/wizards/wizard_several_partners.py:0 +#, python-format +msgid "You must select a client to be able to add it to the reservation " +msgstr "Debe seleccionar un cliente para poder añadirlo a la reserva " + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "" +"You must select a confirmation template in the email configuration menu of " +"the property" +msgstr "" +"Debe seleccionar una plantilla de confirmación en el menú de configuración " +"de correo electrónico de la propiedad" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "" +"You must select a exit template in the email configuration menu of the " +"property" +msgstr "" +"Debe seleccionar una plantilla de salida en el menú de configuración de " +"correo electrónico de la propiedad" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "" +"You must select a modification template in the email configuration menu of " +"the property" +msgstr "" +"Debe seleccionar una plantilla de modificación en el menú de configuración " +"de correo electrónico de la propiedad" + +#. module: pms +#: model:mail.template,subject:pms.cancelled_reservation_email +msgid "Your reservation in ${object.pms_property_id.name} has been cancelled" +msgstr "Su reserva en ${object.pms_property_id.name} ha sido cancelada" + +#. module: pms +#: model:mail.template,subject:pms.modified_reservation_email +msgid "Your reservation in ${object.pms_property_id.name} has been modified" +msgstr "Su reserva en ${object.pms_property_id.name} ha sido modificada" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +#: model_terms:ir.ui.view,arch_db:pms.res_partner_view_form +msgid "ZIP" +msgstr "Código Postal" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__residence_zip +#: model:ir.model.fields,field_description:pms.field_pms_property__zip +#: model:ir.model.fields,field_description:pms.field_res_partner__zip +#: model:ir.model.fields,field_description:pms.field_res_users__zip +msgid "Zip" +msgstr "Código Postal" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__residence_zip +#: model:ir.model.fields,field_description:pms.field_res_partner__residence_zip +#: model:ir.model.fields,field_description:pms.field_res_users__residence_zip +msgid "Zip of residence" +msgstr "Código postal de residencia" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__residence_zip +#: model:ir.model.fields,help:pms.field_pms_property__residence_zip +#: model:ir.model.fields,help:pms.field_res_partner__residence_zip +#: model:ir.model.fields,help:pms.field_res_users__residence_zip +msgid "Zip of the guest's residence" +msgstr "Código postal de la residencia del huésped" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room__is_shared_room +msgid "allows you to reserve units smaller than the room itself (eg beds)" +msgstr "" +"Permite reservar unidades más pequeñas que la propia habitación (por " +"ejemplo, camas)" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__free_room_ids +msgid "" +"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" +msgstr "" +"Permite enviar diferentes parámetros en el contexto (checkin(required), " +"checkout(required), room_type_id, ubication_id, capacity, amenity_ids and / " +"or pricelist_id) y devolver habitaciones disponibles" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__availability +msgid "" +"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" +msgstr "" +"Permite enviar diferentes parámetros en el contexto (checkin(required), " +"checkout(required), room_type_id, ubication_id, capacity,amenity_ids and / " +"or pricelist_id) verifique la disponibilidad del hotel" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +msgid "" +"availability rules\n" +" will be overwritten:" +msgstr "" +"Reglas de disponibilidad\n" +" que van a ser sobre escritas:" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "booking agency with wrong configuration: " +msgstr "Agencia de reservas con configuración incorrecta: " + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_split_join_swap_wizard__checkout +msgid "checkout in reservation" +msgstr "Checkout reserba" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__residence_city +#: model:ir.model.fields,field_description:pms.field_res_partner__residence_city +#: model:ir.model.fields,field_description:pms.field_res_users__residence_city +msgid "city of residence" +msgstr "ciudad de residencia" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_folio_error +#: model_terms:ir.ui.view,arch_db:pms.portal_folio_success +msgid "close" +msgstr "Cerrar" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_prechekin_folio +msgid "completed" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "email" +msgstr "Correo Electrónico" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__birthdate_date +msgid "host birthdate" +msgstr "Fecha de nacimiento" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__firstname +msgid "host firstname" +msgstr "Nombre" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__gender +msgid "host gender" +msgstr "Género" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__lastname +msgid "host lastname" +msgstr "Apellido 1" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__nationality_id +msgid "host nationality" +msgstr "Nacionalidad" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__lastname2 +msgid "host second lastname" +msgstr "Apellido 2" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__partner_incongruences +msgid "" +"indicates that some partner fields on the checkin do not " +"correspond to that of the associated partner" +msgstr "" +"Indica que algunos campos del cliente no se corresponden con el del partner " +"asociado" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__partner_incongruences +msgid "" +"indicates that some partner fields on the folio do not " +"correspond to that of the associated partner" +msgstr "" +"Indica que algunos campos de la ficha no se corresponden con el del partner " +"asociado" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__partner_incongruences +msgid "" +"indicates that some partner fields on the reservation do not " +"correspond to that of the associated partner" +msgstr "" +"Indica que algunos campos de la reserva no se corresponden con el del " +"partner asociado" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "mobile" +msgstr "móvil" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.reservation_wizard +msgid "or" +msgstr "o" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__partner_incongruences +#: model:ir.model.fields,field_description:pms.field_pms_folio__partner_incongruences +#: model:ir.model.fields,field_description:pms.field_pms_reservation__partner_incongruences +msgid "partner_incongruences" +msgstr "partner_incongruences" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_pms_room_form +msgid "pms Room" +msgstr "Sala pms" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +msgid "" +"pricelist items\n" +" will be overwritten:" +msgstr "" +"items de la tarifa\n" +" serán sobrescritas:" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "" +"these are the billing information associated with the\n" +" booking client or the company (if a company " +"is\n" +" assigned). If you want to bill an " +"independent contact,\n" +" you can select it in the billing assistant" +msgstr "" +"Estes son los datos de facturación con el\n" +" cliente de la reserva o con la compañía(Si " +"una compañía es\n" +" asignada). Si quiere facturar un contacto " +"independiente,\n" +" puede seleccionarlo en el asistente de " +"facturación" + +#~ msgid " Pending" +#~ msgstr " Pendiente" + +#~ msgid "Additional info" +#~ msgstr "Información adicional" + +#~ msgid "Availability rules" +#~ msgstr "Reglas de Disponibilidad" + +#~ msgid "Birth Date" +#~ msgstr "Fecha de Nacimiento" + +#~ msgid "Blacklisted Phone Is Mobile" +#~ msgstr "El teléfono en la lista negra es un móvil" + +#~ msgid "Blacklisted Phone is Phone" +#~ msgstr "El teléfono en la lista negra es un teléfono fijo" + +#~ msgid "Company database ID" +#~ msgstr "ID de la base de datos de la empresa" + +#~ msgid "Covering" +#~ msgstr "Cubriendo" + +#, python-format +#~ msgid "" +#~ "Daily Plan must have fixed price, only one property and its items must be " +#~ "daily" +#~ msgstr "" +#~ "El Plan Diario debe tener precio fijo, sólo una propiedad y sus artículos " +#~ "deben ser diarios" + +#~ msgid "Days" +#~ msgstr "Días" + +#~ msgid "Doc. Expedition Date/Doc. Validity Date" +#~ msgstr "Doc. Fecha de expedición/Doc. Fecha de validez" + +#~ msgid "Doc. Number" +#~ msgstr "Doc. Número" + +#~ msgid "Doc. Type" +#~ msgstr "Doc. Tipo" + +#, python-format +#~ msgid "Error in autoinvoicing folio: " +#~ msgstr "Error en folio de autofacturación: " + +#, python-format +#~ msgid "Error in autoinvoicing invoice: " +#~ msgstr "Error en la autofacturación de la factura: " + +#~ msgid "Expedition date" +#~ msgstr "Fecha de expedición" + +#~ msgid "" +#~ "Field used to store sanitized phone number. Helps speeding up searches " +#~ "and comparisons." +#~ msgstr "" +#~ "Campo utilizado para almacenar el número de teléfono saneado. Ayuda a " +#~ "acelerar las búsquedas y las comparaciones." + +#~ msgid "" +#~ "If the sanitized phone number is on the blacklist, the contact won't " +#~ "receive mass mailing sms anymore, from any list" +#~ msgstr "" +#~ "Si el número de teléfono saneado está en la lista negra, el contacto no " +#~ "recibirá más sms de envío masivo, de ninguna lista" + +#~ msgid "" +#~ "Indicates if a blacklisted sanitized phone number is a mobile number. " +#~ "Helps distinguish which number is blacklisted when there is " +#~ "both a mobile and phone field in a model." +#~ msgstr "" +#~ "Indica si un número de teléfono saneado de la lista negra es un número de " +#~ "móvil. Ayuda a distinguir qué número está en la lista " +#~ "negra cuando hay tanto un campo de móvil " +#~ "como de teléfono en un modelo." + +#~ msgid "" +#~ "Indicates if a blacklisted sanitized phone number is a phone number. " +#~ "Helps distinguish which number is blacklisted when there is " +#~ "both a mobile and phone field in a model." +#~ msgstr "" +#~ "Indica si un número de teléfono saneado de la lista negra es un número de " +#~ "teléfono. Ayuda a distinguir qué número está en la lista " +#~ "negra cuando hay tanto un campo de móvil " +#~ "como de teléfono en un modelo." + +#~ msgid "Invoice Status; it can be: upselling, invoiced, to invoice, no" +#~ msgstr "" +#~ "Estado de la factura; puede ser: upselling, facturado, a facturar, no" + +#, python-format +#~ msgid "" +#~ "It is forbidden to modify the following fields\n" +#~ " in a locked folio (fields already invoiced):\n" +#~ "%s" +#~ msgstr "" +#~ "Está prohibido modificar los siguientes campos\n" +#~ " en un folio bloqueado (campos ya facturados):\n" +#~ "%s" + +#, python-format +#~ msgid "Journal %s is not allowed to be used for normal invoices" +#~ msgstr "El diario %s no puede utilizarse para facturas normales" + +#~ msgid "Lastname" +#~ msgstr "Apellidos" + +#~ msgid "Name*" +#~ msgstr "Nombre*" + +#, python-format +#~ msgid "Not invoiced due to pending amounts" +#~ msgstr "No facturado debido a importes pendientes" + +#~ msgid "Page" +#~ msgstr "Página" + +#~ msgid "Phone Blacklisted" +#~ msgstr "Teléfono en la lista negra" + +#~ msgid "Property Ubications" +#~ msgstr "Zonas del Hotel" + +#~ msgid "Property settings summary" +#~ msgstr "Resumen de la configuración del Hotel" + +#~ msgid "Related Company" +#~ msgstr "Compañía relacionada" + +#~ msgid "Room Occupied" +#~ msgstr "Habitación Ocupada" + +#~ msgid "SMS Delivery error" +#~ msgstr "Error Entrega SMS" + +#~ msgid "Sanitized Number" +#~ msgstr "Número Saneado" + +#~ msgid "" +#~ "Save and Continue\n" +#~ " " +#~ msgstr "" +#~ "Guardar y continuar\n" +#~ " " + +#~ msgid "Select an option" +#~ msgstr "Seleccione una opción" + +#~ msgid "Service By Day" +#~ msgstr "Servicio por día" + +#~ msgid "Spanish Residence permit" +#~ msgstr "Permiso de residencia Español" + +#~ msgid "" +#~ "Technical field to know if vat partner is syncronized with this document" +#~ msgstr "" +#~ "Campo técnico para saber si el socio iva está sincronizado con este " +#~ "documento" + +#~ msgid "Upselling Opportunity" +#~ msgstr "Oportunidad de venta adiccional" + +#~ msgid "Vat Syncronized" +#~ msgstr "Recipiente Sincronizado" + +#~ msgid "of" +#~ msgstr "de" + +#~ msgid "partner
" +#~ msgstr "socio
" + +#~ msgid "partner data to remain to be covered" +#~ msgstr "datos de los socios que deben seguir cubiertos" + +#~ msgid "" +#~ ".
\n" +#~ " Your check-in has been successful." +#~ msgstr "" +#~ "
\n" +#~ " Su registro se ha realizado correctamente." + +#~ msgid "" +#~ ".
\n" +#~ " Do your check-in now and save time." +#~ msgstr "" +#~ "
\n" +#~ " Haz tu check-in ahora y ahorra tiempo." + +#~ msgid "" +#~ "
\n" +#~ " You can check if all the data has been saved " +#~ "correctly" +#~ msgstr "" +#~ "
\n" +#~ " Puedes comprobar si todos los datos se han " +#~ "guardado correctamente" + +#~ msgid "" +#~ "
\n" +#~ " This is our quick registration system. " +#~ "In a few steps you will be able to register your data in an agile, simple " +#~ "and secure way, avoiding queues at reception.\n" +#~ " If you register your data in our system, your " +#~ "passage through reception will be much faster, being able to " +#~ "enjoy the comfort of your room right away." +#~ msgstr "" +#~ "
\n" +#~ " Este es nuestrosistema de registro rápido. En pocos pasos podrás registrar tus datos de forma ágil, " +#~ "sencilla y segura,evitando colas en recepción.\n" +#~ " Si registras tus datos en nuestro sistema, tu " +#~ "paso por recepción será mucho más rápido, pudiendo disfrutar al " +#~ "instante de la comodidad de tu habitación." + +#~ msgid " Send message" +#~ msgstr " Enviar mensaje" + +#~ msgid "" +#~ "\n" +#~ " Send Invitations" +#~ msgstr "" +#~ "\n" +#~ " Enviar Invitaciones" + +#~ msgid "" +#~ "\n" +#~ " CHECK IN NOW!" +#~ msgstr "" +#~ "\n" +#~ " ¡ REGÍSTRESE AHORA!" + +#~ msgid "Salesperson" +#~ msgstr "Responsable:" + +#~ msgid "Action" +#~ msgstr "Acción" + +#~ msgid "After" +#~ msgstr "Después" + +#~ msgid "All reservations" +#~ msgstr "Todas las reservas" + +#~ msgid "Automated Actions" +#~ msgstr "Acciones automatizadas" + +#~ msgid "Automated Mails" +#~ msgstr "Correos automatizados" + +#~ msgid "Automatic Checkout on past reservations" +#~ msgstr "Checkout automático para reservas ya pasadas" + +#~ msgid "Before" +#~ msgstr "Antes" + +#~ msgid "Call Center" +#~ msgstr "Call Center" + +#~ msgid "Channel" +#~ msgstr "Canal de venta" + +#~ msgid "Channel Type" +#~ msgstr "Tipo del Canal de Venta" + +#~ msgid "Custom label" +#~ msgstr "Valores Personalizados" + +#~ msgid "" +#~ "Doc. Number:\n" +#~ "
" +#~ msgstr "" +#~ "Doc. Número:\n" +#~ "
" + +#~ msgid "" +#~ "Doc. Type:\n" +#~ "
" +#~ msgstr "" +#~ "Doc. Tipo:\n" +#~ "
" + +#~ msgid "Door" +#~ msgstr "Puerta" + +#, python-format +#~ msgid "Down Payment: %s" +#~ msgstr "Adelanto: %s" + +#~ msgid "" +#~ "Email:\n" +#~ "
" +#~ msgstr "" +#~ "Correo electrónico:\n" +#~ "
" + +#~ msgid "Hour" +#~ msgstr "Hora" + +#~ msgid "If true, this document type is check by vat number" +#~ msgstr "" +#~ "Si es verdadero, este tipo de documento se verifica por número de IVA" + +#~ msgid "" +#~ "If you wish, you can share with the rest of the guests the access to " +#~ "their check-in so that they can fill it out." +#~ msgstr "" +#~ "Si lo desea, puede compartir con el resto de huéspedes el acceso a su " +#~ "check-in para que puedan rellenarlo." + +#~ msgid "Impacts quota" +#~ msgstr "Impacts quota" + +#~ msgid "In the act" +#~ msgstr "En el acto" + +#~ msgid "Indicates if the automated mail is active" +#~ msgstr "Indica si el correo automatizado está activo" + +#~ msgid "Mail" +#~ msgstr "Correo" + +#~ msgid "Only allowed if the field of sale channel channel_type is 'direct'" +#~ msgstr "" +#~ "Solo se permite si el campo del canal de venta channel_type es 'direct'" + +#~ msgid "Related customer with Folio Sale Line" +#~ msgstr "Cliente relacionado con Folio Sale Line" + +#~ msgid "Salesperson" +#~ msgstr "Vendedor" + +#, python-format +#~ msgid "The Property are mandatory in the reservation" +#~ msgstr "La propiedad es obligatoria en la reserva" + +#, python-format +#~ msgid "The Sale Channel does not correspond to the agency's" +#~ msgstr "El canal de venta no corresponde con el de la agencia" + +#~ msgid "The internal user in charge of this contact." +#~ msgstr "El usuario interno a cargo de este contacto." + +#, python-format +#~ msgid "The qty (%s) is wrong. The quantity pending to invoice is %s" +#~ msgstr "" +#~ "La cantidad (%s) es incorrecta. La cantidad pendiente de facturar es %s" + +#~ msgid "The user who created the folio" +#~ msgstr "Usuario que creo la ficha" + +#~ msgid "This line has been taken into account in the avail quota" +#~ msgstr "Este registro se tiene en cuenta para calcular la disponibilidad" + +#~ msgid "User who manages the reservation" +#~ msgstr "Usuario que gestiona la reserva" + +#~ msgid "" +#~ "you cannot have more than one room with the same name in the same property" +#~ msgstr "" +#~ "No puede tener más de una habitación con el mismo nombre en la misma " +#~ "propiedad" + +#~ msgid "Reservation Sequence" +#~ msgstr "Referencia de la Reserva" + +#~ msgid "The sequence that formed the name of the reservation." +#~ msgstr "La secuencia que forma el nombre de la reserva." + +#~ msgid "" +#~ "\n" +#~ " Used for closing of rooms for extra privacy.\n" +#~ " " +#~ msgstr "" +#~ "\n" +#~ "\t\tUtilizado para el cierre de habitaciones para una mayor privacidad\n" +#~ "\t" + +#~ msgid "" +#~ "\n" +#~ " Used for closing of rooms which require a maintenance. " +#~ "You can specify\n" +#~ " the reason in the own reservation.\n" +#~ " " +#~ msgstr "" +#~ "\n" +#~ "\t\tUtilizado para el cierre de habitaciones que requieren mantenimiento. " +#~ "Puede especificar la razón en la propia reserva.\n" +#~ "\t\t" + +#~ msgid "Add possible Customer" +#~ msgstr "Añadir contacto encontrado" + +#~ msgid "Amount from %s to %s %s \n" +#~ msgstr "Importe deste %s hasta %s %s\n" + +#~ msgid "Apply changes" +#~ msgstr "Aplicar Cambios" + +#~ msgid "Board service has been changed from folio" +#~ msgstr "El regimen ha sido modificado desde la ficha" + +#~ msgid "" +#~ "Conversion between Units of Measure can only occur if they belong to the " +#~ "same category. The conversion will be made based on the ratios." +#~ msgstr "" +#~ "La conversión entre unidades de medida solo pueden ocurrir si pertenecen " +#~ "a la misma categoría. La conversión se realizará basada en las " +#~ "proporciones." + +#~ msgid "Country State" +#~ msgstr "Provincia" + +#~ msgid "Customer Reference" +#~ msgstr "Referencia del Cliente" + +#~ msgid "Date from %s to %s \n" +#~ msgstr "Fecha deste %s hasta %s \n" + +#~ msgid "Deleted payment: %s %s " +#~ msgstr "Pago Eliminado: %s %s " + +#~ msgid "Economica" +#~ msgstr "Economic" + +#~ msgid "Estandar" +#~ msgstr "Estandar" + +#~ msgid "First Floor" +#~ msgstr "Primer Piso" + +#~ msgid "Folio payments" +#~ msgstr "Pagos de la Ficha" + +#~ msgid "Ground Floor" +#~ msgstr "Planta Baja" + +#~ msgid "Has Folios Outstanding" +#~ msgstr "Tiene Pagos Pendientes" + +#~ msgid "" +#~ "In Standard Price & AVCO: value of the product (automatically computed in " +#~ "AVCO).\n" +#~ " In FIFO: value of the last unit that left the stock " +#~ "(automatically computed).\n" +#~ " Used to value the product when the purchase cost is not known (e." +#~ "g. inventory adjustment).\n" +#~ " Used to compute margins on sale orders." +#~ msgstr "" +#~ "El Precio Estándar y AVCO: valor del producto (calculado automáticamente " +#~ "en AVCO)\n" +#~ "\tEn FIFO: valor de la última unidad que salió de stock (calculado " +#~ "automáticamente).\n" +#~ "\tSe utiliza para valorar el producto cuando el precio de compra se " +#~ "desconoce (por ej. ajuste de inventario).\n" +#~ "\tUsado para calcular márgenes en pedidos de ventas." + +#~ msgid "Invoice Contact" +#~ msgstr "Contacto de Facturación" + +#~ msgid "Journal from %s to %s" +#~ msgstr "Diario desde %s hasta %s" + +#~ msgid "Outstanding Folios Debits Widget" +#~ msgstr "Widget para los Débitos pendientes de al ficha" + +#~ msgid "Outstanding credits in Folio" +#~ msgstr "Créditos pendientes en Folio" + +#~ msgid "Payment %s modified: \n" +#~ msgstr "Pago % sModificado:\n" + +#~ msgid "Payment Deleted" +#~ msgstr "Pago Borrado" + +#~ msgid "" +#~ "Payment of %s %s registered from %s using %s " +#~ "payment method" +#~ msgstr "Pago de %s %s registrado desde %s usando %s método de pago" + +#~ msgid "Prices/Discounts have been changed from folio" +#~ msgstr "Los precios/descuentos han sido cambiados de ficha" + +#~ msgid "Prop. Demo Views" +#~ msgstr "Prop. Demo Vistas" + +#~ msgid "Reservation Name" +#~ msgstr "Notas de la Reserva" + +#~ msgid "Second Floor" +#~ msgstr "Segundo Piso" + +#~ msgid "The company for Account Jouarnal" +#~ msgstr "La compañía por defecto para este usuario." + +#~ msgid "" +#~ "There is a customer with this email or mobile, do you want to add it to " +#~ "the reservation?" +#~ msgstr "" +#~ "Hay un cliente con este email o móvil, ¿quieres añadirlo a la reserva?" + +#~ msgid "Total debt" +#~ msgstr "Deuda total" + +#~ msgid "Tour Operator" +#~ msgstr "Tour Operador" + +#~ msgid "host state" +#~ msgstr "Provincia" diff --git a/pms/i18n/pms.pot b/pms/i18n/pms.pot new file mode 100644 index 0000000000..c556b49b08 --- /dev/null +++ b/pms/i18n/pms.pot @@ -0,0 +1,12368 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pms +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "" +"\n" +" Compute error: The first room line date should\n" +" be the same as the checkin date!\n" +" " +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "" +"\n" +" Compute error: The last room line date should\n" +" be the previous day of the checkout date!\n" +" " +msgstr "" + +#. module: pms +#: code:addons/pms/models/res_partner.py:0 +#, python-format +msgid "" +"\n" +" Agency must have a PMS pricelist, please review the\n" +" pricelists configuration (%s) to allow it for PMS,\n" +" or the pricelist selected for the agencies: %s\n" +" " +msgstr "" + +#. module: pms +#: model:room.closure.reason,description:pms.pms_room_closure_reason_vip_privacy +msgid "" +"\n" +" Used for closing rooms for extra privacy.\n" +" " +msgstr "" + +#. module: pms +#: model:room.closure.reason,description:pms.pms_room_closure_reason_maintenance +msgid "" +"\n" +" Used for closing rooms which requires a maintenance. You can specify\n" +" the reason in the own reservation.\n" +" " +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_res_company__pms_invoice_downpayment_policy +msgid "" +"\n" +" - Manual: Downpayment invoice will be created manually\n" +" - All: Downpayment invoice will be created automatically\n" +" - Current Month: Downpayment invoice will be created automatically\n" +" only for reservations with checkout date past of current month\n" +" " +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__product_variant_count +msgid "# Product Variants" +msgstr "" + +#. module: pms +#: model:mail.template,subject:pms.confirmed_reservation_email +msgid "" +"${object.company_id.name} has confirmed your reservation in " +"${object.pms_property_id.name}" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__penalty_late +msgid "% Penalty Late" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__penalty_noshow +msgid "% Penalty No Show" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_checkin_partner.py:0 +#, python-format +msgid "%s is not a valid %s identifier" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_service_line.py:0 +#, python-format +msgid "%s limit exceeded for %s" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_checkin_partner.py:0 +#, python-format +msgid "%s not found in checkins (%s)" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation_line.py:0 +#, python-format +msgid "%s: No room available in %s <-> %s." +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation_line.py:0 +#, python-format +msgid "%s: No room type available" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.report_folio_document +msgid "&nbsp;on&nbsp;" +msgstr "" + +#. module: pms +#: model:ir.actions.report,print_report_name:pms.action_report_pms_pro_forma_invoice +msgid "'PRO-FORMA - %s' % (object.name)" +msgstr "" + +#. module: pms +#: model:ir.actions.report,print_report_name:pms.action_report_folio +#: model:ir.actions.report,print_report_name:pms.action_traveller_report +msgid "" +"(object.state in ('draft', 'sent') and 'Quotation - %s' % (object.name)) or " +"'Order - %s' % (object.name)" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.report_folio_document +msgid ") Nights" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid ", at" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation_precheckin +msgid "" +".
\n" +" Do your check-in now and save time." +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "" +"\n" +"
\n" +" Categoría:" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +msgid "Days:" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +msgid "Rules to apply:" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation_precheckin +msgid "" +"
\n" +" This is our quick registration system. In a few steps you will be able to register your data in an agile, simple and secure way, avoiding queues at reception.\n" +" If you register your data in our system, your passage through reception will be much faster, being able to enjoy the comfort of your room right away." +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_not_checkin +msgid "
Email:" +msgstr "" + +#. module: pms +#: model:mail.template,body_html:pms.precheckin_invitation_email +msgid "" +"
\n" +" Do your check-in now and save time.\n" +"
\n" +" Access our quick registration system. In a few steps you will be able to register your data in an agile, simple and secure way, avoiding queues at reception.\n" +" If you register your data in our system, your passage through reception will be much faster, being able to enjoy the comfort of your room right away.\n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +"
Check-in\n" +"
\n" +"
\"Hacer
\n" +"
\n" +"
\n" +"
\n" +" " +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folio_invitations +msgid "" +"\n" +" Room Type:\n" +" " +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation +msgid "" +" Reservations by folio: " +"" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid " Cancelled Reservation!" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.folio_portal_template +msgid " Download" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_folio_page_payment +msgid " Pay Now" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_folio_page_payment +msgid " Paid" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.folio_portal_template +msgid "Send message" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "" +"" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.folio_portal_template +msgid " Print" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid " OverBooking" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid " Reselling" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation_detail +msgid "Status:" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.booking_engine +msgid "" +"\n" +" Your reservation in ${object.pms_property_id.name} has been modified\n" +" \n" +"
\n" +" See you soon,
\n" +" \n" +"
\n" +" ${object.company_id.name}\n" +"
\n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +"
Reservation Details
\n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +"
\n" +"
\n" +"
From ${object.first_checkin}
\n" +"
To ${object.last_checkout}
\n" +"
TZ ${object.pms_property_id.tz}
\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
Rooms:
\n" +" % for reservation in object.reservation_ids:\n" +" ${reservation.room_type_id.name}
\n" +" % endfor\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
Price: ${object.amount_total} ${object.pms_property_id.country_id.currency_id.symbol}
\n" +"
\n" +"
\n" +"
\n" +" % if object.pms_property_id.mail_information\n" +"
\n" +"
Additional Information
\n" +"

${object.pms_property_id.mail_information|safe}

\n" +" % endif\n" +"
\n" +"
\n" +"
\n" +" \n" +"
\n" +" Questions about the reservation?\n" +"
Please contact with us:
\n" +"
    \n" +"
  • ${object.pms_property_id.name}
  • \n" +" % if object.pms_property_id.partner_id.email\n" +"
  • Mail: ${object.pms_property_id.partner_id.email}
  • \n" +" % endif\n" +" % if object.pms_property_id.partner_id.phone\n" +"
  • Phone: ${object.pms_property_id.partner_id.phone}
  • \n" +" % endif\n" +" % if object.pms_property_id.partner_id.mobile\n" +"
  • Mobile: ${object.pms_property_id.partner_id.mobile}
  • \n" +" % endif\n" +"
\n" +"
\n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" % if object.pms_property_id.privacy_policy\n" +" \n" +" \n" +"
\n" +" ${object.pms_property_id.privacy_policy|safe}\n" +"
\n" +" % endif\n" +" \n" +" \n" +" % if object.company_id\n" +" \n" +" \n" +"
\n" +" Sent by ${object.company_id.name}\n" +"
\n" +"
\n" +" % endif\n" +" \n" +" \n" +" " +msgstr "" + +#. module: pms +#: model:mail.template,body_html:pms.confirmed_reservation_email +msgid "" +"\n" +" \n" +" \n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +" \n" +" \n" +"
\n" +"
\n" +" % if object.pms_property_id.partner_id.street\n" +"

${object.pms_property_id.partner_id.street}

\n" +" % endif\n" +" % if object.pms_property_id.partner_id.street2\n" +"

${object.pms_property_id.partner_id.street2}

\n" +" % endif\n" +"

${object.pms_property_id.partner_id.zip}

\n" +"

${object.pms_property_id.partner_id.city}

\n" +"

${object.pms_property_id.partner_id.country_id.name}

\n" +"
\n" +"
\n" +"
\n" +"
\n" +" Hello ${object.partner_id.name or ''},
\n" +" We are happy to confirm your reservation in ${object.pms_property_id.name}\n" +"
\n" +"
\n" +" See you soon,
\n" +" \n" +"
\n" +" ${object.company_id.name}\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
Reservation Details
\n" +" \n" +" \n" +"\n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +"
\n" +"
\n" +"
From ${object.first_checkin}
\n" +"
To ${object.last_checkout}
\n" +"
TZ ${object.pms_property_id.tz}
\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
Rooms:
\n" +" % for reservation in object.reservation_ids:\n" +" ${reservation.room_type_id.name}
\n" +" % endfor\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
Price: ${object.amount_total} ${object.pms_property_id.country_id.currency_id.symbol}
\n" +"
\n" +"
\n" +"
\n" +" % if object.pms_property_id.mail_information\n" +"
\n" +"
Additional Information
\n" +" ${object.pms_property_id.mail_information|safe}\n" +" % endif\n" +"
\n" +"
\n" +"
\n" +"
\n" +" Do your check-in now and save time.\n" +"
\n" +" Access our quick registration system. In a few steps you will be able to register your data in an agile, simple and secure way, avoiding queues at reception.\n" +" If you register your data in our system, your passage through reception will be much faster, being able to enjoy the comfort of your room right away.\n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +"
Check-in\n" +"
\n" +"
\"Hacer
\n" +"
\n" +"
\n" +"
\n" +"
\n" +" \n" +"
\n" +" Questions about the reservation?\n" +"
Please contact with us:
\n" +"
    \n" +"
  • ${object.pms_property_id.name}
  • \n" +" % if object.pms_property_id.partner_id.email\n" +"
  • Mail: ${object.pms_property_id.partner_id.email}
  • \n" +" % endif\n" +" % if object.pms_property_id.partner_id.phone\n" +"
  • Phone: ${object.pms_property_id.partner_id.phone}
  • \n" +" % endif\n" +" % if object.pms_property_id.partner_id.mobile\n" +"
  • Mobile: ${object.pms_property_id.partner_id.mobile}
  • \n" +" % endif\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
\n" +" % if object.pms_property_id.privacy_policy\n" +" \n" +" \n" +"
\n" +" ${object.pms_property_id.privacy_policy|safe}\n" +"
\n" +" % endif\n" +"
\n" +" % if object.company_id\n" +" \n" +" \n" +"
\n" +" Sent by ${object.company_id.name}\n" +"
\n" +"
\n" +" % endif\n" +"
\n" +" " +msgstr "" + +#. module: pms +#: model:mail.template,body_html:pms.cancelled_reservation_email +msgid "" +"\n" +" \n" +" \n" +" \n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +" \n" +" \n" +"
\n" +"
\n" +" % if object.pms_property_id.partner_id.street\n" +"

${object.pms_property_id.partner_id.street}

\n" +" % endif\n" +" % if object.pms_property_id.partner_id.street2\n" +"

${object.pms_property_id.partner_id.street2}

\n" +" % endif\n" +"

${object.pms_property_id.partner_id.zip}

\n" +"

${object.pms_property_id.partner_id.city}

\n" +"

${object.pms_property_id.partner_id.country_id.name}

\n" +"
\n" +"
\n" +"
\n" +"
\n" +" Hello ${object.partner_id.name or ''},
\n" +" Your reservation at ${object.pms_property_id.name} has been cancelled.\n" +"
\n" +"
\n" +"
\n" +" \n" +" \n" +"
\n" +"
\n" +" \n" +"
\n" +"
If you have questions please contact with us:
\n" +"
    \n" +"
  • ${object.pms_property_id.name}
  • \n" +" % if object.pms_property_id.partner_id.email\n" +"
  • Mail: ${object.pms_property_id.partner_id.email}
  • \n" +" % endif\n" +" % if object.pms_property_id.partner_id.phone\n" +"
  • Phone: ${object.pms_property_id.partner_id.phone}
  • \n" +" % endif\n" +" % if object.pms_property_id.partner_id.mobile\n" +"
  • Mobile: ${object.pms_property_id.partner_id.mobile}
  • \n" +" % endif\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
\n" +" % if object.pms_property_id.privacy_policy\n" +" \n" +" \n" +"
\n" +" ${object.pms_property_id.privacy_policy}\n" +"
\n" +" % endif\n" +"
\n" +" % if object.company_id\n" +" \n" +" \n" +"
\n" +" Sent by ${object.company_id.name}\n" +"
\n" +"
\n" +" % endif\n" +"
\n" +" " +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "" +"A closure reason is mandatory when reservation type is 'out of service'" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "A customer/s has this email or mobile, do you want to add it?" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room__description_sale +msgid "" +"A description of the Product that you want to communicate to your " +"customers. This description will be copied to every Sales Order, Delivery " +"Order and Customer Invoice/Credit Note" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__description_sale +msgid "" +"A description of the Product that you want to communicate to your customers." +" This description will be copied to every Sales Order, Delivery Order and " +"Customer Invoice/Credit Note" +msgstr "" + +#. module: pms +#: code:addons/pms/models/res_partner.py:0 +#: code:addons/pms/models/res_partner.py:0 +#, python-format +msgid "A document identification is required" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "A journal must be specified for the acquirer %s." +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "A payment acquirer is required to create a transaction." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_advance_payment_inv__advance_payment_method +msgid "" +"A standard invoice is issued with all the order lines ready " +"for invoicing, according to their invoicing policy" +" (based on ordered or delivered quantity)." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__type +msgid "" +"A storable product is a product for which you manage stock. The Inventory app has to be installed.\n" +"A consumable product is a product for which stock is not managed.\n" +"A service is a non-material product you provide." +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "A transaction can't be linked to folios having different currencies." +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "A transaction can't be linked to folios having different partners." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__aeat_anonymous_cash_customer +msgid "AEAT - Anonymous customer" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__aeat_identification_type +msgid "AEAT Identification type" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__user_ids +msgid "Accepted Users" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__access_warning +#: model:ir.model.fields,field_description:pms.field_pms_folio__access_warning +#: model:ir.model.fields,field_description:pms.field_pms_reservation__access_warning +msgid "Access warning" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__property_account_payable_id +msgid "Account Payable" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__property_account_receivable_id +msgid "Account Receivable" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_advance_payment_inv__deposit_account_id +msgid "Account used for deposits" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__message_needaction +#: model:ir.model.fields,field_description:pms.field_pms_folio__message_needaction +#: model:ir.model.fields,field_description:pms.field_pms_property__message_needaction +#: model:ir.model.fields,field_description:pms.field_pms_reservation__message_needaction +#: model:ir.model.fields,field_description:pms.field_pms_room_type__message_needaction +msgid "Action Needed" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_amenity__active +#: model:ir.model.fields,field_description:pms.field_pms_amenity_type__active +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan__active +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__active +#: model:ir.model.fields,field_description:pms.field_pms_property__active +#: model:ir.model.fields,field_description:pms.field_pms_room__active +#: model:ir.model.fields,field_description:pms.field_pms_room_type__active +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class__active +#: model:ir.model.fields,field_description:pms.field_pms_team_member__active +msgid "Active" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__active_lang_count +msgid "Active Lang Count" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__activity_ids +#: model:ir.model.fields,field_description:pms.field_pms_folio__activity_ids +#: model:ir.model.fields,field_description:pms.field_pms_property__activity_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__activity_ids +#: model:ir.model.fields,field_description:pms.field_pms_room_type__activity_ids +msgid "Activities" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__activity_exception_decoration +#: model:ir.model.fields,field_description:pms.field_pms_folio__activity_exception_decoration +#: model:ir.model.fields,field_description:pms.field_pms_property__activity_exception_decoration +#: model:ir.model.fields,field_description:pms.field_pms_reservation__activity_exception_decoration +#: model:ir.model.fields,field_description:pms.field_pms_room_type__activity_exception_decoration +msgid "Activity Exception Decoration" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__activity_state +#: model:ir.model.fields,field_description:pms.field_pms_folio__activity_state +#: model:ir.model.fields,field_description:pms.field_pms_property__activity_state +#: model:ir.model.fields,field_description:pms.field_pms_reservation__activity_state +#: model:ir.model.fields,field_description:pms.field_pms_room_type__activity_state +msgid "Activity State" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__activity_type_icon +#: model:ir.model.fields,field_description:pms.field_pms_folio__activity_type_icon +#: model:ir.model.fields,field_description:pms.field_pms_property__activity_type_icon +#: model:ir.model.fields,field_description:pms.field_pms_reservation__activity_type_icon +#: model:ir.model.fields,field_description:pms.field_pms_room_type__activity_type_icon +msgid "Activity Type Icon" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_board_service +msgid "Add Board Service to reservations" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.several_partners_wizard +msgid "Add Customer" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_service +msgid "Add Service to reservations" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Add a note" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Add a product" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Add a section" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Add customer" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_amenity__is_add_code_room_name +msgid "Add in room name" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.booking_engine +msgid "Add to Folio" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__mail_information +msgid "Additional information of the mail" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__type +msgid "Address Type" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Aditional Mail Information" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_team_member__pms_role__administrative +msgid "Administrative" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__administrative_user_id +msgid "Administrative Manager" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_board_service_line__adults +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type_line__adults +#: model:ir.model.fields,field_description:pms.field_pms_reservation__adults +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__adults +msgid "Adults" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_board_service_line.py:0 +#: code:addons/pms/models/pms_board_service_room_type_line.py:0 +#, python-format +msgid "Adults or Children must be checked" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_advanced_filters_wizard +#: model:ir.ui.menu,name:pms.menu_pms_advanced_filters +msgid "Advanced Filters" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.advanced_filters_wizard +msgid "Advanced filters" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__aeat_identification +msgid "Aeat Identification" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__product_template__consumed_on__after +msgid "After night" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__age +msgid "Age" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.pms_agency_action +#: model:ir.ui.menu,name:pms.pms_agency_menu +msgid "Agencies" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__agency_id +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__agency_id +#: model:ir.model.fields,field_description:pms.field_pms_folio__agency_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__agency_id +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +#: model_terms:ir.ui.view,arch_db:pms.res_partner_view_form +msgid "Agency" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__agency_id +#: model:ir.model.fields,help:pms.field_pms_booking_engine__agency_id +#: model:ir.model.fields,help:pms.field_pms_reservation__agency_id +msgid "Agency that made the reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__res_company__pms_invoice_downpayment_policy__all +msgid "All" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_cancelation_rule__apply_on_late__all +#: model:ir.model.fields.selection,name:pms.selection__pms_cancelation_rule__apply_on_noshow__all +msgid "All Days" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.wizard_folio_changes_view_form +msgid "All days" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__lang +msgid "" +"All the emails and documents sent to this contact will be translated in this" +" language." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__allowed_method_ids +msgid "Allowed Method" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.res_users_view_form +msgid "Allowed Properties" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__allowed_reservation_ids +msgid "Allowed Reservations" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__allowed_room_type_ids +msgid "Allowed Room Types" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__allowed_room_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__allowed_room_ids +#: model:ir.model.fields,field_description:pms.field_pms_wizard_reservation_lines_split__allowed_room_ids +msgid "Allowed Rooms" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__allowed_service_ids +msgid "Allowed Services" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_product_pricelist_item__allowed_board_service_product_ids +msgid "Allowed board service products" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_product_pricelist_item__allowed_board_service_room_type_ids +msgid "Allowed board service room types" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__allowed_cancel +msgid "Allowed cancel" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__allowed_checkin +msgid "Allowed checkin" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__allowed_checkout +msgid "Allowed checkout" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__allowed_pricelist_ids +msgid "Allowed pricelists" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__allowed_rooms_sources +msgid "Allowed rooms source" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__allowed_rooms_target +msgid "Allowed rooms target" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__allowed_board_services +msgid "Allowed services" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_board_service.py:0 +#, python-format +msgid "Already exists another Board Service with the same code and properties" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_room_type_class.py:0 +#, python-format +msgid "" +"Already exists another room type class with the same code and properties" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_room_type.py:0 +#, python-format +msgid "Already exists another room type with the same code and properties" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_property.py:0 +#, python-format +msgid "Alreay exist other property with this code: %s (%s)" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_property.py:0 +#, python-format +msgid "Alreay exist other property with this ref: %s (%s)" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__res_partner__invoice_to_agency__always +msgid "Always" +msgstr "" + +#. module: pms +#: model:ir.ui.menu,name:pms.menu_action_pms_room_amenity_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_form +msgid "Amenities" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_amenity_type__pms_amenity_ids +msgid "Amenities In This Category" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_amenity_type__pms_amenity_ids +msgid "Amenities included in this type" +msgstr "" + +#. module: pms +#: model:ir.ui.menu,name:pms.menu_amenity +#: model_terms:ir.ui.view,arch_db:pms.pms_amenity_view_form +msgid "Amenity" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_amenity__pms_amenity_type_id +msgid "Amenity Category" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_amenity__name +#: model:ir.model.fields,help:pms.field_pms_amenity__name +msgid "Amenity Name" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_amenity_type +#: model_terms:ir.ui.view,arch_db:pms.pms_amenity_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_room_amenity_type_view_form +msgid "Amenity Type" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_amenity_type__name +#: model:ir.model.fields,help:pms.field_pms_amenity_type__name +msgid "Amenity Type Name" +msgstr "" + +#. module: pms +#: model:ir.ui.menu,name:pms.menu_action_pms_room_amenity_type_view_form +msgid "Amenity Types" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_board_service__amount +#: model:ir.model.fields,field_description:pms.field_pms_board_service_line__amount +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type__amount +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type_line__amount +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__amount +msgid "Amount" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__commission_amount +msgid "Amount corresponding to commission" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__invoices_paid +msgid "Amount of invoices paid" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service_line__day_qty +msgid "Amount to be consumed per day" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__untaxed_amount_to_invoice +msgid "Amount to invoice" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Amounts" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__analytic_account_id +msgid "Analytic Account" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_account_analytic_distribution +msgid "Analytic Account Distribution" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_account_analytic_line +msgid "Analytic Line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__analytic_tag_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__analytic_tag_ids +#: model:ir.model.fields,field_description:pms.field_pms_service__analytic_tag_ids +msgid "Analytic Tags" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__analytic_line_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__analytic_line_ids +msgid "Analytic lines" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.product_pricelist_item_view_tree +msgid "Applicable on" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_on_all_week +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_on_all_week +msgid "Apply Availability Rule for the whole week" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_on_friday +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_on_friday +msgid "Apply Availability Rule on fridays" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_on_monday +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_on_monday +msgid "Apply Availability Rule on mondays" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_on_saturday +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_on_saturday +msgid "Apply Availability Rule on saturdays" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_on_sunday +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_on_sunday +msgid "Apply Availability Rule on sundays" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_on_thursday +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_on_thursday +msgid "Apply Availability Rule on thursdays" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_on_tuesday +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_on_tuesday +msgid "Apply Availability Rule on tuesdays" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_on_wednesday +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_on_wednesday +msgid "Apply Availability Rule on wednesdays" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_new_checkin +msgid "Apply Checkin Update" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_new_checkout +msgid "Apply Checkout Update" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_partner_id +msgid "Apply Customer" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_discount +msgid "Apply Discount update" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__change_from_date +msgid "Apply From" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_price +msgid "Apply Price update" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__apply_pricelist +#: model:ir.model.fields,field_description:pms.field_res_partner__apply_pricelist +#: model:ir.model.fields,field_description:pms.field_res_users__apply_pricelist +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_pricelist_id +msgid "Apply Pricelist" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__change_to_date +msgid "Apply To" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +msgid "Apply and close" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +msgid "Apply and continue" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type__by_default +msgid "Apply by Default" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_closed +msgid "Apply changes to Closed" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_closed_arrival +msgid "Apply changes to Closed Arrival" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_closed_departure +msgid "Apply changes to Closed Departure" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_max_avail +msgid "Apply changes to Max. Avail." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_max_stay +msgid "Apply changes to Max. Stay" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_max_stay_arrival +msgid "Apply changes to Max. Stay Arrival" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_min_stay +msgid "Apply changes to Min. Stay" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_min_stay_arrival +msgid "Apply changes to Min. Stay Arrival" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_quota +msgid "Apply changes to Quota" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__apply_pricelists_on +msgid "Apply pricelists on" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service_line__adults +#: model:ir.model.fields,help:pms.field_pms_board_service_room_type_line__adults +msgid "Apply service to adults" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service_line__children +#: model:ir.model.fields,help:pms.field_pms_board_service_room_type_line__children +msgid "Apply service to children" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__state__arrival_delayed +msgid "Arrival Delayed" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__default_arrival_hour +#: model:ir.model.fields,field_description:pms.field_pms_reservation__arrival_hour +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Arrival Hour" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__arrival_hour +msgid "Arrival Hour (HH:MM)" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__pms_folio_ids +#: model:ir.model.fields,help:pms.field_res_partner__pms_folio_ids +#: model:ir.model.fields,help:pms.field_res_users__pms_folio_ids +msgid "Associated Folios" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__pms_checkin_partner_ids +#: model:ir.model.fields,help:pms.field_res_partner__pms_checkin_partner_ids +#: model:ir.model.fields,help:pms.field_res_users__pms_checkin_partner_ids +msgid "Associated checkin partners" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__pms_reservation_ids +#: model:ir.model.fields,help:pms.field_res_partner__pms_reservation_ids +#: model:ir.model.fields,help:pms.field_res_users__pms_reservation_ids +msgid "Associated reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room__ubication_id +msgid "At which ubication the room is located." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__message_attachment_count +#: model:ir.model.fields,field_description:pms.field_pms_folio__message_attachment_count +#: model:ir.model.fields,field_description:pms.field_pms_property__message_attachment_count +#: model:ir.model.fields,field_description:pms.field_pms_reservation__message_attachment_count +#: model:ir.model.fields,field_description:pms.field_pms_room_type__message_attachment_count +msgid "Attachment Count" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__product_template_attribute_value_ids +msgid "Attribute Values" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_res_company__privacy_policy +msgid "Authorization by the user for themanage of their personal data" +msgstr "" + +#. module: pms +#: model:ir.actions.server,name:pms.autoinvoicing_downpayments_ir_actions_server +#: model:ir.cron,cron_name:pms.autoinvoicing_downpayments +#: model:ir.cron,name:pms.autoinvoicing_downpayments +msgid "Auto Invoicing DownPayments" +msgstr "" + +#. module: pms +#: model:ir.actions.server,name:pms.autoinvoicing_folios_ir_actions_server +#: model:ir.cron,cron_name:pms.autoinvoicing_folios +#: model:ir.cron,name:pms.autoinvoicing_folios +msgid "Auto Invoicing Folios" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__is_canceled_auto_mail +msgid "Auto Send Cancellation Mail" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__is_confirmed_auto_mail +msgid "Auto Send Confirmation Mail" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Auto Send Email" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__is_exit_auto_mail +msgid "Auto Send Exit Mail" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__is_modified_auto_mail +msgid "Auto Send Modification Mail" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__preconfirm +msgid "Auto confirm to Save" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__autoinvoice_date +msgid "Autoinvoice Date" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_automated_mails +msgid "Automatic Mails" +msgstr "" + +#. module: pms +#: model:ir.actions.server,name:pms.nocheckout_reservations_ir_actions_server +#: model:ir.cron,cron_name:pms.nocheckout_reservations +#: model:ir.cron,name:pms.nocheckout_reservations +msgid "Automatic No Checkout Reservations" +msgstr "" + +#. module: pms +#: model:ir.actions.server,name:pms.noshow_reservations_ir_actions_server +#: model:ir.cron,cron_name:pms.noshow_reservations +#: model:ir.cron,name:pms.noshow_reservations +msgid "Automatic No Show Reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__avail_id +msgid "Avail record" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability__avail_rule_ids +msgid "Avail record rules" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__avail_id +msgid "Availability Day" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__availability_plan_id +#: model:ir.model.fields,field_description:pms.field_product_pricelist__availability_plan_id +#: model:ir.model.fields.selection,name:pms.selection__pms_massive_changes_wizard__massive_changes_on__availability_plan +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +msgid "Availability Plan" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan__name +msgid "Availability Plan Name" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.availability_plan_rule_view_tree_action +msgid "Availability Plan Rules" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_product_pricelist__availability_plan_id +msgid "Availability Plan for which the pricelist is included" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__availability_plan_ids +msgid "Availability Plan to apply massive changes" +msgstr "" + +#. module: pms +#: model:ir.ui.menu,name:pms.reservation_availability_plan_rules_menu +#: model_terms:ir.ui.view,arch_db:pms.availability_plan_rule_view_form +msgid "Availability Plans" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__availability_results +#: model:ir.model.fields,help:pms.field_pms_booking_engine__availability_results +msgid "Availability Results" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan__rule_ids +#: model_terms:ir.ui.view,arch_db:pms.availability_view_form +msgid "Availability Rules" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_product_pricelist__pms_sale_channel_ids +msgid "Available Channels" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_product_pricelist__is_pms_available +msgid "Available in PMS" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__available_in_pos +msgid "Available in POS" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__num_rooms_available +msgid "Available rooms" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Avatar" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__avail_readonly +msgid "Avialability Readonly" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_account_journal__avoid_autoinvoice_downpayment +#: model:ir.model.fields,help:pms.field_account_journal__avoid_autoinvoice_downpayment +msgid "Avoid autoinvoice downpayment" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__avoid_mails +msgid "Avoid comunication mails" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__avoid_simplified_max_amount_downpayment +msgid "Avoid simplified invoice max amount downpayment" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__bank_account_count +msgid "Bank" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__payment_ids +msgid "Bank Payments" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_account_bank_statement +msgid "Bank Statement" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_account_bank_statement_line +msgid "Bank Statement Line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__bank_ids +msgid "Banks" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__barcode +#: model:ir.model.fields,field_description:pms.field_pms_room_type__barcode +msgid "Barcode" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__product_template__consumed_on__before +msgid "Before night" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__bill_rooms +#: model:ir.model.fields,help:pms.field_folio_advance_payment_inv__bill_rooms +msgid "Bill Rooms" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__bill_services +#: model:ir.model.fields,help:pms.field_folio_advance_payment_inv__bill_services +msgid "Bill Services" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__partner_invoice_ids +msgid "Billing addresses" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__partner_invoice_id +msgid "Billing contact" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Birth Date *" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__birthdate_date +#: model:ir.model.fields,field_description:pms.field_pms_property__birthdate_date +#: model:ir.model.fields,field_description:pms.field_res_partner__birthdate_date +#: model:ir.model.fields,field_description:pms.field_res_users__birthdate_date +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "Birthdate" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__is_blacklisted +msgid "Blacklist" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__blocked +msgid "Blocked" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#: code:addons/pms/models/pms_reservation_line.py:0 +#, python-format +msgid "Blocked reservations can't be modified" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_pms_board_service_room_type_view +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__is_board_service +#: model:ir.model.fields,field_description:pms.field_pms_board_service_line__pms_board_service_id +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type__pms_board_service_id +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__board_service_room_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__board_service_room_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__board_service_room_id +#: model:ir.model.fields,field_description:pms.field_product_pricelist_item__board_service_room_type_id +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_report_view_tree +msgid "Board Service" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_board_service__default_code +msgid "Board Service Code" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_service__board_service_line_id +#: model_terms:ir.ui.view,arch_db:pms.pms_board_service_form +#: model_terms:ir.ui.view,arch_db:pms.pms_board_service_room_type_form +msgid "Board Service Line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__board_service_line_id +msgid "Board Service Line in which this service is included" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_board_service__board_service_line_ids +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type__board_service_line_ids +msgid "Board Service Lines" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_board_service__name +#: model:ir.model.fields,help:pms.field_pms_board_service__name +msgid "Board Service Name" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__board_price +#: model:ir.model.fields,field_description:pms.field_product_product__board_price +msgid "Board Service Price" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type_line__pms_board_service_room_type_id +msgid "Board Service Room" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service_room_type_line__pms_board_service_room_type_id +msgid "Board Service Room Type in which this line is included" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service_room_type__pms_board_service_id +msgid "Board Service corresponding to this Board Service Room Type" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service_line__pms_board_service_id +msgid "Board Service in which this line is included" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_board_service_room_type +msgid "Board Service included in Room" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__board_service_room_type_ids +msgid "Board Service included in room type" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio_availability_wizard__board_service_room_id +#: model:ir.model.fields,help:pms.field_pms_reservation__board_service_room_id +#: model:ir.model.fields,help:pms.field_pms_reservation_duplicate__board_service_room_id +msgid "Board Service included in the room" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.open_pms_board_service_form_tree +#: model:ir.model,name:pms.model_pms_board_service +#: model:ir.model.fields,field_description:pms.field_pms_room_type__board_service_room_type_ids +#: model:ir.model.fields.selection,name:pms.selection__pms_massive_changes_wizard__apply_pricelists_on__board_services +#: model:ir.ui.menu,name:pms.menu_open_pms_board_service_form_tree +#: model_terms:ir.ui.view,arch_db:pms.pms_room_type_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "Board Services" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_board_service__pms_board_service_room_type_ids +msgid "Board Services Room Type" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service__pms_board_service_room_type_ids +msgid "" +"Board Services Room Type corresponding to this Board Service,One board " +"service for several room types" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__board_service +msgid "Board service" +msgstr "" + +#. module: pms +#. openerp-web +#: code:addons/pms/static/src/xml/reservation_group_button_views.xml:0 +#: model:ir.model.fields,field_description:pms.field_pms_num_rooms_selection__booking_engine_id +#: model:ir.ui.menu,name:pms.menu_pms_booking_engine +#, python-format +msgid "Booking Engine" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_booking_engine +msgid "Booking engine" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__message_bounce +msgid "Bounce" +msgstr "" + +#. module: pms +#: model:pms.board.service,name:pms.pms_board_service_breakfast +msgid "BreakFast" +msgstr "" + +#. module: pms +#: model:product.product,name:pms.pms_service_breakfast_buffet +#: model:product.template,name:pms.pms_service_breakfast_buffet_product_template +msgid "Breakfast Buffet" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "By Day" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "By Month" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "By Week" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "CIF:" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__can_image_1024_be_zoomed +msgid "Can Image 1024 be zoomed" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__can_image_variant_1024_be_zoomed +msgid "Can Variant Image 1024 be zoomed" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__purchase_ok +msgid "Can be Purchased" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__sale_ok +msgid "Can be Sold" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__can_create_folio +msgid "Can create folio" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.advanced_filters_wizard +#: model_terms:ir.ui.view,arch_db:pms.booking_duplicate +#: model_terms:ir.ui.view,arch_db:pms.booking_engine +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.reservation_wizard +#: model_terms:ir.ui.view,arch_db:pms.several_partners_wizard +#: model_terms:ir.ui.view,arch_db:pms.view_folio_advance_payment_inv +#: model_terms:ir.ui.view,arch_db:pms.wizard_folio_changes_view_form +#: model_terms:ir.ui.view,arch_db:pms.wizard_payment_folio_view_form +msgid "Cancel" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__cancel_datetime +msgid "Cancel Date" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Cancel Folio" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Cancel Penalty" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Cancel Reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_res_company__cancel_penalty_product_id +msgid "Cancel penalty product" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_service_line__cancel_discount +msgid "Cancelation Discount" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__cancel_discount +msgid "Cancelation Discount (%)" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_product_pricelist__cancelation_rule_id +msgid "Cancelation Policy" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_product_pricelist__cancelation_rule_id +msgid "Cancelation Policy included in the room" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__name +msgid "Cancelation Rule" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_pms_cancelation_rule +#: model:ir.model,name:pms.model_pms_cancelation_rule +#: model:ir.ui.menu,name:pms.menu_pms_cancelation_rule +#: model_terms:ir.ui.view,arch_db:pms.pms_cancelation_rule_form +msgid "Cancelation Rules" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__property_canceled_template +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Cancellation Email" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__property_canceled_template +msgid "Cancellation email template" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_checkin_partner__state__cancel +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__state__cancel +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__state__cancel +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_kanban_view +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Cancelled" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.report_invoice_document +msgid "Cancelled Invoice" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Cancelled Rooms" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.report_invoice_document +msgid "Cancelled Simplified Invoice" +msgstr "" + +#. module: pms +#: code:addons/pms/models/ir_config_parameter.py:0 +#, python-format +msgid "Cannot delete this parameter" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room__capacity +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_kanban +msgid "Capacity" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.company_view_form +msgid "Cardex Settings" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__statement_line_ids +msgid "Cash Payments" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_res_partner_id_number__category_id +msgid "Category" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__route_from_categ_ids +msgid "Category Routes" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__pos_categ_id +msgid "Category used in the Point of Sale." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__cancelled_reason +msgid "Cause of cancelled" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_room_closure_reason +#: model:ir.model.fields,field_description:pms.field_pms_folio__out_service_description +#: model:ir.model.fields,field_description:pms.field_pms_reservation__out_service_description +msgid "Cause of out of service" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.wizard_folio_changes_view_form +msgid "Change" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__apply_day_qty +msgid "Change cuantity service per day" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__channel_ids +msgid "Channels" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__check_adults +msgid "Check Adults" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__checkin +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__checkin +msgid "Check In" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__checkout +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__checkout +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Check Out" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__is_origin_channel_check_visible +msgid "Check force update origin visible" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__is_company +msgid "Check if the contact is a company, otherwise it is a person" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__to_weight +msgid "" +"Check if the product should be weighted using the hardware scale " +"integration." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_duplicate__occupied_room +msgid "Check if the room is occupied" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__available_in_pos +msgid "Check if you want this product to appear in the Point of Sale." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_res_company__check_min_partner_data_invoice +msgid "Check minimum partner data for invoices" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_res_company__check_min_partner_data_invoice +msgid "" +"Check minimum partner data for invoices:\n" +" - VAT, name, street, city, country" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__employee +msgid "Check this box if this contact is an Employee." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__aeat_anonymous_cash_customer +msgid "Check this for anonymous cash customer. AEAT communication" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Check-in hours" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__checkin +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation +msgid "Checkin" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__pending_checkin_data +#: model:ir.model.fields,field_description:pms.field_pms_reservation__pending_checkin_data +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Checkin Data" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Checkin Date" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +msgid "Checkin Detail" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_several_partners_wizard__checkin_partner_id +msgid "Checkin Partner" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__email +msgid "Checkin Partner Email" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__identifier +msgid "Checkin Partner Id" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__image_128 +msgid "Checkin Partner Image, it corresponds with Partner Image associated" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__mobile +msgid "Checkin Partner Mobile" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__phone +msgid "Checkin Partner Phone" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__checkin_partner_ids +#: model:ir.model.fields,field_description:pms.field_pms_property__pms_checkin_partner_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__checkin_partner_ids +#: model:ir.model.fields,field_description:pms.field_res_partner__pms_checkin_partner_ids +#: model:ir.model.fields,field_description:pms.field_res_users__pms_checkin_partner_ids +msgid "Checkin Partners" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__checkin_partner_pending_count +msgid "Checkin Pending Num" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__checkin_sequence_id +msgid "Checkin Sequence" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +msgid "Checkin by" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +msgid "Checkin by Day" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +msgid "Checkin by Month" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +msgid "Checkin by Week" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__checkin_partner_count +msgid "Checkin counter" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__checkin +msgid "Checkin date" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_split_join_swap_wizard__checkin +msgid "Checkin in reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__arrival +msgid "Checkin partner arrival date and time" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__departure +msgid "Checkin partner departure date and time" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__name +msgid "Checkin partner name" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__sii_simplified_invoice +msgid "" +"Checking this mark, invoices done to this partner will be sent to SII as " +"simplified invoices." +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#: model:ir.actions.act_window,name:pms.action_checkin_partner +#: model:ir.ui.menu,name:pms.menu_pms_checkin_partner +#, python-format +msgid "Checkins" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +msgid "Checkins Today" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +msgid "Checkins Tomorrow" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +msgid "Checkins to 7 days" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__checkout +#: model:ir.model.fields.selection,name:pms.selection__pms_property__default_invoicing_policy__checkout +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation +msgid "Checkout" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +msgid "Checkout by" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +msgid "Checkout by Day" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +msgid "Checkout by Month" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +msgid "Checkout by Week" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__checkout +msgid "Checkout date" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__res_company__pms_invoice_downpayment_policy__checkout_past_month +msgid "Checkout past month" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability__child_avail_ids +msgid "Child Avails" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__child_ids +msgid "Child Properties" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room__child_ids +msgid "Child Rooms" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability__child_avail_ids +msgid "Child availabilities for this availability" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room__child_ids +msgid "Child rooms of the room" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_board_service_line__children +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type_line__children +#: model:ir.model.fields,field_description:pms.field_pms_reservation__children +msgid "Children" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__children_occupying +msgid "Children occupying" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.several_partners_wizard +msgid "Choose a customer if you want to add it to the reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__residence_city +#: model:ir.model.fields,field_description:pms.field_pms_property__city +#: model:ir.model.fields,field_description:pms.field_res_partner__city +#: model:ir.model.fields,field_description:pms.field_res_users__city +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +#: model_terms:ir.ui.view,arch_db:pms.res_partner_view_form +msgid "City" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__residence_city +#: model:ir.model.fields,help:pms.field_pms_property__residence_city +#: model:ir.model.fields,help:pms.field_res_partner__residence_city +#: model:ir.model.fields,help:pms.field_res_users__residence_city +msgid "City of the guest's residence" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class__name +msgid "Class Name" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__class_id +msgid "Class to which the room type belongs" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_folio_payment +msgid "Close" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__closed +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__closed +msgid "Closed" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__closed_arrival +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__closed_arrival +msgid "Closed Arrival" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__closed_departure +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__closed_departure +msgid "Closed Departure" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__closure_reason_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__closure_reason_id +msgid "Closure Reason" +msgstr "" + +#. module: pms +#: model:ir.ui.menu,name:pms.menu_pms_room_closure_reason_form_tree +msgid "Closure Reasons" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Closure reason" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__default_code +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class__default_code +msgid "Code" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__color +#: model:ir.model.fields,field_description:pms.field_pms_room_type__color +msgid "Color Index" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__combination_indices +msgid "Combination Indices" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Comments about the customer" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__commercial_partner_id +msgid "Commercial Entity" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__commission +#: model:ir.model.fields,field_description:pms.field_pms_property__default_commission +#: model:ir.model.fields,field_description:pms.field_res_partner__default_commission +#: model:ir.model.fields,field_description:pms.field_res_users__default_commission +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_tree +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_tree +msgid "Commission" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Commission Amount" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__commission_amount +msgid "Commission amount" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__commission_percent +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Commission percent (%)" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_res_company +msgid "Companies" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__ref_company_ids +msgid "Companies that refers to partner" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__company_id +#: model:ir.model.fields,field_description:pms.field_pms_folio__company_id +#: model:ir.model.fields,field_description:pms.field_pms_property__company_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__company_id +#: model:ir.model.fields,field_description:pms.field_pms_room_type__company_id +#: model:ir.model.fields,field_description:pms.field_pms_service__company_id +#: model:ir.model.fields,field_description:pms.field_product_pricelist__company_id +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +msgid "Company" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__company_name +msgid "Company Name" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__commercial_company_name +msgid "Company Name Entity" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__company_type +msgid "Company Type" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_product_pricelist__company_id +msgid "Company to which the pricelist belongs" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__company_id +msgid "Company to which the reservation belongs" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__company_id +msgid "Company to which the service belongs" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__contact_address +msgid "Complete Address" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__ratio_checkin_data +msgid "Complete cardex" +msgstr "" + +#. module: pms +#: model:pms.room.type.class,name:pms.pms_room_type_class_conference_room +msgid "Conference" +msgstr "" + +#. module: pms +#: model:pms.room.type,name:pms.pms_room_type_conference_room +#: model:product.product,name:pms.pms_room_type_conference_room_product_product +msgid "Conference Room" +msgstr "" + +#. module: pms +#: model:ir.ui.menu,name:pms.pms_configuration_menu +msgid "Configuration" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Confirm" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Confirm Assigned Room" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Confirm Sale" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__confirmation_date +msgid "Confirmation Date" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__property_confirmed_template +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Confirmation Email" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__property_confirmed_template +msgid "Confirmation email template" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__state__confirm +msgid "Confirmed" +msgstr "" + +#. module: pms +#: model:pms.amenity.type,name:pms.pms_amenity_type_connectivity +msgid "Connectivity" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__consumed_on +#: model:ir.model.fields,field_description:pms.field_product_product__consumed_on +#: model:ir.model.fields,field_description:pms.field_product_template__consumed_on +msgid "Consumed" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_massive_changes_wizard__date_types__consumption_dates +msgid "Consumption Dates" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_res_partner +#: model_terms:ir.ui.view,arch_db:pms.folio_portal_template +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation_detail +msgid "Contact" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Contact Invoiced" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_kanban_view +msgid "Contact image" +msgstr "" + +#. module: pms +#: model:ir.ui.menu,name:pms.pms_contacts_menu +msgid "Contacts" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__standard_price +msgid "Cost" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__cost_currency_id +msgid "Cost Currency" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__cost_method +msgid "Costing Method" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__payment_token_count +msgid "Count Payment Token" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__count_alternative_free_rooms +msgid "Count alternative free rooms" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__message_bounce +msgid "Counter of the number of bounced emails for this contact" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_res_partner_id_category__country_ids +msgid "Countries" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_res_country +#: model:ir.model.fields,field_description:pms.field_pms_property__country_id +#: model:ir.model.fields,field_description:pms.field_res_partner__country_id +#: model:ir.model.fields,field_description:pms.field_res_partner_id_number__country_id +#: model:ir.model.fields,field_description:pms.field_res_users__country_id +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +#: model_terms:ir.ui.view,arch_db:pms.res_partner_view_form +msgid "Country" +msgstr "" + +#. module: pms +#: code:addons/pms/models/res_partner_id_number.py:0 +#, python-format +msgid "Country is not allowed for this document type" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__residence_country_id +#: model:ir.model.fields,field_description:pms.field_pms_property__residence_country_id +#: model:ir.model.fields,field_description:pms.field_res_partner__residence_country_id +#: model:ir.model.fields,field_description:pms.field_res_users__residence_country_id +msgid "Country of residence" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__document_country_id +#: model:ir.model.fields,help:pms.field_res_partner_id_number__country_id +msgid "Country of the document" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__residence_country_id +msgid "Country of the guest's residence" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +msgid "Create Date" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.booking_engine +msgid "Create Folio" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__advance_payment_method +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.view_folio_advance_payment_inv +msgid "Create Invoice" +msgstr "" + +#. module: pms +#: model_terms:ir.actions.act_window,help:pms.action_pms_move_out_invoice_type +msgid "Create a customer invoice" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.booking_duplicate +msgid "Create and Close" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.booking_duplicate +msgid "Create and Continue" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.view_folio_advance_payment_inv +msgid "Create and View Invoice" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +msgid "Create by" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +msgid "Create by Day" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +msgid "Create by Month" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +msgid "Create by Week" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_view_folio_advance_payment_inv +msgid "Create invoices" +msgstr "" + +#. module: pms +#: model_terms:ir.actions.act_window,help:pms.action_pms_move_out_invoice_type +msgid "" +"Create invoices, register payments and keep track of the discussions with " +"your customers." +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Created By" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__create_uid +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__create_uid +#: model:ir.model.fields,field_description:pms.field_ir_pms_property__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_advanced_filters_wizard__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_amenity__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_amenity_type__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_automated_mails__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_availability__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_board_service__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_board_service_line__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type_line__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_folio__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_num_rooms_selection__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_property__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_reservation__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_room__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_room_type__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_sale_channel__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_service__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_service_line__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_several_partners_wizard__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_team_member__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_ubication__create_uid +#: model:ir.model.fields,field_description:pms.field_pms_wizard_reservation_lines_split__create_uid +#: model:ir.model.fields,field_description:pms.field_room_closure_reason__create_uid +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__create_uid +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__create_uid +msgid "Created by" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__create_date +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__create_date +#: model:ir.model.fields,field_description:pms.field_ir_pms_property__create_date +#: model:ir.model.fields,field_description:pms.field_pms_advanced_filters_wizard__create_date +#: model:ir.model.fields,field_description:pms.field_pms_amenity__create_date +#: model:ir.model.fields,field_description:pms.field_pms_amenity_type__create_date +#: model:ir.model.fields,field_description:pms.field_pms_automated_mails__create_date +#: model:ir.model.fields,field_description:pms.field_pms_availability__create_date +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan__create_date +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__create_date +#: model:ir.model.fields,field_description:pms.field_pms_board_service__create_date +#: model:ir.model.fields,field_description:pms.field_pms_board_service_line__create_date +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type_line__create_date +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__create_date +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__create_date +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__create_date +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__create_date +#: model:ir.model.fields,field_description:pms.field_pms_folio__create_date +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__create_date +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__create_date +#: model:ir.model.fields,field_description:pms.field_pms_num_rooms_selection__create_date +#: model:ir.model.fields,field_description:pms.field_pms_property__create_date +#: model:ir.model.fields,field_description:pms.field_pms_reservation__create_date +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__create_date +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__create_date +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__create_date +#: model:ir.model.fields,field_description:pms.field_pms_room__create_date +#: model:ir.model.fields,field_description:pms.field_pms_room_type__create_date +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class__create_date +#: model:ir.model.fields,field_description:pms.field_pms_sale_channel__create_date +#: model:ir.model.fields,field_description:pms.field_pms_service__create_date +#: model:ir.model.fields,field_description:pms.field_pms_service_line__create_date +#: model:ir.model.fields,field_description:pms.field_pms_several_partners_wizard__create_date +#: model:ir.model.fields,field_description:pms.field_pms_team_member__create_date +#: model:ir.model.fields,field_description:pms.field_pms_ubication__create_date +#: model:ir.model.fields,field_description:pms.field_pms_wizard_reservation_lines_split__create_date +#: model:ir.model.fields,field_description:pms.field_room_closure_reason__create_date +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__create_date +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__create_date +msgid "Created on" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Creation Date" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__credit_card_details +#: model:ir.model.fields,field_description:pms.field_pms_reservation__credit_card_details +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Credit Card Details" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__credit_limit +msgid "Credit Limit" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.report_invoice_document +msgid "Credit Note" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__currency_id +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__currency_id +#: model:ir.model.fields,field_description:pms.field_pms_folio__currency_id +#: model:ir.model.fields,field_description:pms.field_pms_property__currency_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__currency_id +#: model:ir.model.fields,field_description:pms.field_pms_room_type__currency_id +#: model:ir.model.fields,field_description:pms.field_pms_service__currency_id +#: model:ir.model.fields,field_description:pms.field_pms_service_line__currency_id +msgid "Currency" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_advance_payment_inv__currency_id +msgid "Currency used in invoices" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "Current Booking" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.res_users_view_form +msgid "Current Property" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__partner_id +msgid "Current property" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__qty_available +msgid "" +"Current quantity of products.\n" +"In a context with a single Stock Location, this includes goods stored at this Location, or any of its children.\n" +"In a context with a single Warehouse, this includes goods stored in the Stock Location of this Warehouse, or any of its children.\n" +"stored in the Stock Location of the Warehouse of this Shop, or any of its children.\n" +"Otherwise, this includes goods stored in any Stock Location with 'internal' type." +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation_detail +msgid "Current state of this reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__product_custom_attribute_value_ids +msgid "Custom Values" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__partner_id +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__new_partner_id +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +msgid "Customer" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__email +#: model:ir.model.fields,help:pms.field_pms_reservation__email +msgid "Customer E-mail" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__sale_delay +msgid "Customer Lead Time" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__property_stock_customer +msgid "Customer Location" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__mobile +#: model:ir.model.fields,help:pms.field_pms_reservation__mobile +msgid "Customer Mobile" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__partner_name +#: model:ir.model.fields,field_description:pms.field_pms_reservation__partner_name +msgid "Customer Name" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__property_payment_term_id +msgid "Customer Payment Terms" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__access_url +#: model:ir.model.fields,help:pms.field_pms_folio__access_url +#: model:ir.model.fields,help:pms.field_pms_reservation__access_url +msgid "Customer Portal URL" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__customer_rank +msgid "Customer Rank" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__partner_ref +msgid "Customer Ref" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__deposit_taxes_id +#: model:ir.model.fields,field_description:pms.field_pms_room_type__taxes_id +#: model_terms:ir.ui.view,arch_db:pms.pms_amenity_view_form +msgid "Customer Taxes" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.pms_customer_action +#: model:ir.model.fields,field_description:pms.field_pms_several_partners_wizard__possible_existing_customer_ids +#: model:ir.ui.menu,name:pms.pms_customer_meu +msgid "Customers" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__product_pricelist__pricelist_type__daily +msgid "Daily Plan" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__daily_limit +#: model:ir.model.fields,field_description:pms.field_product_product__daily_limit +#: model:ir.model.fields,field_description:pms.field_product_template__daily_limit +msgid "Daily limit" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__pending_checkin_data +msgid "Data missing at checkin" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__date_order +#: model:ir.model.fields,field_description:pms.field_pms_availability__date +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__date +#: model:ir.model.fields,field_description:pms.field_pms_property__date +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__date +#: model:ir.model.fields,field_description:pms.field_pms_service_line__date +#: model:ir.model.fields,field_description:pms.field_pms_wizard_reservation_lines_split__date +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__date +msgid "Date" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__date_order +msgid "Date Order" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio_availability_wizard__checkout +#: model:ir.model.fields,help:pms.field_pms_reservation_duplicate__checkout +msgid "Date Reservation ends" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio_availability_wizard__checkin +#: model:ir.model.fields,help:pms.field_pms_reservation_duplicate__checkin +msgid "Date Reservation starts " +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__sign_on +msgid "Date and time of the signature" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability__date +msgid "Date for which availability applies" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan_rule__date +msgid "Date for which availability rule applies" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__start_date +msgid "Date from first copy Checkin (reference min checkin folio reservation)" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__document_expedition_date +msgid "Date on which document_type was issued" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__date_order +msgid "Date on which folio is sold" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__confirmation_date +msgid "Date on which the folio is confirmed." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__date_types +msgid "Date types" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__cancel_datetime +msgid "Date when the reservation was cancelled" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__wizard_folio_changes__modification_type__dates +msgid "Dates" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__dates_incongruence +msgid "Dates incrongruence" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__days_intime +msgid "Days Late" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_res_partner__margin_days_autoinvoice +#: model:ir.model.fields,field_description:pms.field_res_users__margin_days_autoinvoice +msgid "Days from Checkout" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__margin_days_autoinvoice +#: model:ir.model.fields,help:pms.field_res_partner__margin_days_autoinvoice +#: model:ir.model.fields,help:pms.field_res_users__margin_days_autoinvoice +msgid "Days from Checkout to generate the invoice" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_cancelation_rule__apply_on_late +msgid "" +"Days on which the cancelation rule applies when the reason is late arrival. " +"Can be first, all days or specify the days." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_cancelation_rule__apply_on_noshow +msgid "" +"Days on which the cancelation rule applies when the reason is no show. Can " +"be first, all days or specify the days." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__days_to_checkin +msgid "Days to Checkin" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__days_to_checkout +msgid "Days to Checkout" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__deduct_down_payments +#: model:ir.model.fields,help:pms.field_folio_advance_payment_inv__deduct_down_payments +msgid "Deduct down payments" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__default_invoicing_policy +msgid "Default Invoicing Policy" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__default_max_avail +msgid "Default Max. Availability" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_res_users__pms_property_id +msgid "Default Property" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__default_quota +msgid "Default Quota" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__default_commission +#: model:ir.model.fields,help:pms.field_res_partner__default_commission +#: model:ir.model.fields,help:pms.field_res_users__default_commission +msgid "Default commission" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__supplier_taxes_id +msgid "Default taxes used when buying the product." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__taxes_id +msgid "Default taxes used when selling the product." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__uom_id +msgid "Default unit of measure used for all stock operations." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__uom_po_id +msgid "" +"Default unit of measure used for purchase orders. It must be in the same " +"category as the default unit of measure." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__seller_ids +msgid "Define vendor pricelists." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__trust +msgid "Degree of trust you have in this debtor" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__sale_delay +msgid "" +"Delivery lead time, in days. It's the number of days, promised to the " +"customer, between the confirmation of the sales order and the delivery." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__default_departure_hour +#: model:ir.model.fields,field_description:pms.field_pms_reservation__departure_hour +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Departure Hour" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__departure_hour +msgid "Departure Hour (HH:MM)" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_checkin_partner.py:0 +#, python-format +msgid "Departure date (%s) is prior to arrival on %s" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__state__departure_delayed +msgid "Departure delayed" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__route_ids +msgid "" +"Depending on the modules installed, this will allow you to define the route " +"of the product: whether it will be bought, manufactured, replenished on " +"order, etc." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__name +#: model:ir.model.fields,field_description:pms.field_pms_room_type__description +#: model:ir.model.fields,field_description:pms.field_room_closure_reason__description +#: model_terms:ir.ui.view,arch_db:pms.report_folio_document +msgid "Description" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__name +msgid "Description of folio sale line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__description_pickingout +msgid "Description on Delivery Orders" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__description_picking +msgid "Description on Picking" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__description_pickingin +msgid "Description on Receptions" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_amenity_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_form +msgid "Descriptions" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Detail" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__credit_card_details +msgid "Details of partner credit card" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_amenity__active +msgid "Determines if amenity is active" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_amenity_type__active +msgid "Determines if amenity type is active" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_cancelation_rule__active +msgid "Determines if cancelation rule is active" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room__active +msgid "Determines if room is active" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_num_rooms_selection +msgid "Dinamic Selection based on avails room" +msgstr "" + +#. module: pms +#: model:product.product,name:pms.pms_service_dinner +#: model:product.template,name:pms.pms_service_dinner_product_template +msgid "Dinner" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_sale_channel__channel_type__direct +msgid "Direct" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__channel_type_id +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__channel_type_id +msgid "Direct Sale Channel" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Disc.%" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__discount +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__discount +msgid "Discount" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__discount +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__discount +#: model:ir.model.fields,field_description:pms.field_pms_service_line__discount +msgid "Discount (%)" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__discount +msgid "Discount (€)" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_service__discount +msgid "Discount (€/ud)" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Discount Room" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Discount Services" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service_line__discount +msgid "Discount in the price of the service." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__discount +msgid "Discount of total price" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__discount +msgid "Discount of total price in folio sale line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__discount +msgid "Discount of total price in reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__discount +#: model:ir.model.fields,help:pms.field_pms_booking_engine__discount +msgid "Discount that be applied in total price" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_account_analytic_distribution__display_name +#: model:ir.model.fields,field_description:pms.field_account_analytic_line__display_name +#: model:ir.model.fields,field_description:pms.field_account_bank_statement__display_name +#: model:ir.model.fields,field_description:pms.field_account_bank_statement_line__display_name +#: model:ir.model.fields,field_description:pms.field_account_journal__display_name +#: model:ir.model.fields,field_description:pms.field_account_move__display_name +#: model:ir.model.fields,field_description:pms.field_account_move_line__display_name +#: model:ir.model.fields,field_description:pms.field_account_payment__display_name +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__display_name +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__display_name +#: model:ir.model.fields,field_description:pms.field_ir_config_parameter__display_name +#: model:ir.model.fields,field_description:pms.field_ir_http__display_name +#: model:ir.model.fields,field_description:pms.field_ir_pms_property__display_name +#: model:ir.model.fields,field_description:pms.field_mail_compose_message__display_name +#: model:ir.model.fields,field_description:pms.field_payment_acquirer__display_name +#: model:ir.model.fields,field_description:pms.field_payment_transaction__display_name +#: model:ir.model.fields,field_description:pms.field_pms_advanced_filters_wizard__display_name +#: model:ir.model.fields,field_description:pms.field_pms_amenity__display_name +#: model:ir.model.fields,field_description:pms.field_pms_amenity_type__display_name +#: model:ir.model.fields,field_description:pms.field_pms_automated_mails__display_name +#: model:ir.model.fields,field_description:pms.field_pms_availability__display_name +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan__display_name +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__display_name +#: model:ir.model.fields,field_description:pms.field_pms_board_service__display_name +#: model:ir.model.fields,field_description:pms.field_pms_board_service_line__display_name +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type__display_name +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type_line__display_name +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__display_name +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__display_name +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__display_name +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__display_name +#: model:ir.model.fields,field_description:pms.field_pms_folio__display_name +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__display_name +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__display_name +#: model:ir.model.fields,field_description:pms.field_pms_num_rooms_selection__display_name +#: model:ir.model.fields,field_description:pms.field_pms_property__display_name +#: model:ir.model.fields,field_description:pms.field_pms_reservation__display_name +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__display_name +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__display_name +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__display_name +#: model:ir.model.fields,field_description:pms.field_pms_room__display_name +#: model:ir.model.fields,field_description:pms.field_pms_room_type__display_name +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class__display_name +#: model:ir.model.fields,field_description:pms.field_pms_sale_channel__display_name +#: model:ir.model.fields,field_description:pms.field_pms_service__display_name +#: model:ir.model.fields,field_description:pms.field_pms_service_line__display_name +#: model:ir.model.fields,field_description:pms.field_pms_several_partners_wizard__display_name +#: model:ir.model.fields,field_description:pms.field_pms_team_member__display_name +#: model:ir.model.fields,field_description:pms.field_pms_ubication__display_name +#: model:ir.model.fields,field_description:pms.field_pms_wizard_reservation_lines_split__display_name +#: model:ir.model.fields,field_description:pms.field_product_pricelist__display_name +#: model:ir.model.fields,field_description:pms.field_product_pricelist_item__display_name +#: model:ir.model.fields,field_description:pms.field_product_product__display_name +#: model:ir.model.fields,field_description:pms.field_product_template__display_name +#: model:ir.model.fields,field_description:pms.field_res_company__display_name +#: model:ir.model.fields,field_description:pms.field_res_country__display_name +#: model:ir.model.fields,field_description:pms.field_res_partner__display_name +#: model:ir.model.fields,field_description:pms.field_res_partner_category__display_name +#: model:ir.model.fields,field_description:pms.field_res_partner_id_category__display_name +#: model:ir.model.fields,field_description:pms.field_res_partner_id_number__display_name +#: model:ir.model.fields,field_description:pms.field_res_users__display_name +#: model:ir.model.fields,field_description:pms.field_room_closure_reason__display_name +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__display_name +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__display_name +msgid "Display Name" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__display_type +msgid "Display Type" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Doc. Expedition Date/Doc. Validity Date *" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Doc. Number *" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Doc. Type *" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__document_id +msgid "Document" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "" +"Document\n" +" number" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__document_country_id +msgid "Document Country" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__document_number +msgid "Document Number" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__document_type +msgid "Document Type" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "Document number" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "Document number:" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_res_company__document_partner_required +msgid "Document partner required" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_checkin_partner.py:0 +#, python-format +msgid "Document type and country of document do not match" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_checkin_partner.py:0 +#, python-format +msgid "Document_type has already exists" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_advanced_filters_wizard__pms_domain +msgid "Domain" +msgstr "" + +#. module: pms +#: model:pms.room.type,name:pms.pms_room_type_double +#: model:product.product,name:pms.pms_room_type_double_product_product +msgid "Double" +msgstr "" + +#. module: pms +#: code:addons/pms/wizards/folio_make_invoice_advance.py:0 +#, python-format +msgid "Down Payment" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__amount +msgid "Down Payment Amount" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__fixed_amount +msgid "Down Payment Amount (Fixed)" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__product_id +msgid "Down Payment Product" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "Down Payments" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__folio_advance_payment_inv__advance_payment_method__fixed +msgid "Down payment (fixed amount)" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__folio_advance_payment_inv__advance_payment_method__percentage +msgid "Down payment (percentage)" +msgstr "" + +#. module: pms +#: code:addons/pms/wizards/folio_make_invoice_advance.py:0 +#, python-format +msgid "Down payment of %s%%" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__is_downpayment +msgid "" +"Down payments are made when creating invoices from a folio. They are not " +"copied when duplicating a folio." +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.folio_portal_template +msgid "Download" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__avoid_simplified_max_amount_downpayment +msgid "Downpayment Invoive without limit amount" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_res_company__pms_invoice_downpayment_policy +msgid "Downpayment policy invoce" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_kanban_view +msgid "Draft" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.report_invoice_document +msgid "Draft Invoice" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.report_invoice_document +msgid "Draft Simplified Invoice" +msgstr "" + +#. module: pms +#: model:res.partner.id_category,name:pms.document_type_driving_license +msgid "Driving License" +msgstr "" + +#. module: pms +#. openerp-web +#: code:addons/pms/static/src/xml/pms_base_templates.xml:0 +#, python-format +msgid "Dropdown menu" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_booking_duplicate +msgid "Duplicate Booking" +msgstr "" + +#. module: pms +#: code:addons/pms/wizards/pms_booking_duplicate.py:0 +#, python-format +msgid "Duplicate Folios" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation_line.py:0 +#, python-format +msgid "Duplicated reservation line date" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__email +#: model:ir.model.fields,field_description:pms.field_pms_folio__email +#: model:ir.model.fields,field_description:pms.field_pms_reservation__email +msgid "E-mail" +msgstr "" + +#. module: pms +#: model:pms.room.type,name:pms.pms_room_type_economic +#: model:product.product,name:pms.pms_room_type_economic_product_product +msgid "Economic" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__partner_share +msgid "" +"Either customer (not a user), either shared user. Indicated the current " +"partner is a customer without access or with a limited access created for " +"sharing data." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__email +#: model:ir.model.fields,field_description:pms.field_res_partner__email +#: model:ir.model.fields,field_description:pms.field_res_users__email +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folio_invitations +msgid "Email" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Email (Optional)" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Email Configuration" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Email Templates" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_mail_compose_message +msgid "Email composition wizard" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__employee +msgid "Employee" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_product_pricelist_item__date_end_consumption +msgid "End Date Consumption" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_engine__end_date +msgid "End date for creation of reservations and folios" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_product_pricelist_item__date_end_consumption +msgid "End date to apply daily pricelist items" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__tracking +msgid "Ensure the traceability of a storable product in your warehouse." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__arrival +msgid "Enter" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "Entry date" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_checkin_partner.py:0 +#, python-format +msgid "" +"Error when evaluating the id_category validation code::\n" +" %s \n" +"(%s)" +msgstr "" + +#. module: pms +#: model:res.partner.id_category,name:pms.document_type_european_residence +msgid "European Residence permit" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__checkin_datetime +msgid "Exact Arrival" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__checkout_datetime +msgid "Exact Departure" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__departure +msgid "Exit" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__property_exit_template +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Exit Email" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "Exit date" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "" +"Expedition\n" +" date" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__document_expedition_date +msgid "Expedition Date" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__property_account_expense_id +msgid "Expense Account" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__expense_policy +msgid "" +"Expenses and vendor bills can be re-invoiced to a customer.With this option," +" a validated expense can be re-invoice to a customer at its cost or sales " +"price." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_room_closure_reason__description +msgid "Explanation of the reason for closing a room" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__external_reference +#: model:ir.model.fields,field_description:pms.field_pms_reservation__external_reference +msgid "External Reference" +msgstr "" + +#. module: pms +#: model:product.product,name:pms.pms_service_extra_bed +#: model:product.template,name:pms.pms_service_extra_bed_product_template +msgid "Extra Bed" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room__extra_beds_allowed +msgid "Extra Beds Allowed" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Extra Service" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "Extra Services" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_room.py:0 +#, python-format +msgid "Extra beds can't be greater than allowed beds for this room" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__partner_relationship +msgid "Family relationship between travelers" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_checkin_partner__gender__female +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Female" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_ir_pms_property__field_id +msgid "Field" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__cancelled_reason +msgid "" +"Field indicating type of cancellation. It can be 'late', 'intime' or " +"'noshow'" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__user_ids +msgid "Field related to res.users. Allowed users on the property" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__splitted +msgid "" +"Field that indicates if the reservation is split. A reservation is split " +"when guests don't sleep in the same room every night" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__ratio_checkin_data +msgid "" +"Field that stores the number of checkin partners pending to checkin (with " +"the state = draft)" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__avoid_mails +msgid "Field to indicate not sent mail comunications" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__reservation_order +msgid "Field to order by reservation id" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__date_order +msgid "Field to order by service" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__service_order +msgid "Field to order by service id" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type_class__sequence +msgid "" +"Field used to change the position of the room type classes in tree view." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__sequence +msgid "Field used to change the position of the room types in tree view." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room__sequence +msgid "" +"Field used to change the position of the rooms in tree view.Changing the " +"position changes the sequence" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_ubication__sequence +msgid "" +"Field used to change the position of the ubications in tree view.Changing " +"the position changes the sequence" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__checkin_sequence_id +msgid "Field used to create the name of the checkin partner" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.advanced_filters_wizard +msgid "Filters" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__price_day_total +msgid "Final price" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_cancelation_rule__apply_on_late__first +#: model:ir.model.fields.selection,name:pms.selection__pms_cancelation_rule__apply_on_noshow__first +msgid "First Day" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__first_checkin +msgid "First Folio Checkin" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__firstname +msgid "First Name" +msgstr "" + +#. module: pms +#: model:pms.ubication,name:pms.pms_ubication_1st_floor +msgid "First floor" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__firstname +#: model:ir.model.fields,field_description:pms.field_res_partner__firstname +#: model:ir.model.fields,field_description:pms.field_res_users__firstname +msgid "First name" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__fiscal_position_id +#: model:ir.model.fields,field_description:pms.field_pms_property__property_account_position_id +msgid "Fiscal Position" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_ir_pms_property__value_float +msgid "Float Field Value" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.open_pms_folio1_form_tree_all +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__folio_id +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__folio_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__folio_id +#: model:ir.model.fields,field_description:pms.field_pms_service__folio_id +#: model:ir.model.fields,field_description:pms.field_pms_several_partners_wizard__folio_id +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__folio_id +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__folio_id +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Folio" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folio +msgid "Folio #" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_folio_advance_payment_inv +msgid "Folio Advance Payment Invoice" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_folio_changes +#: model:ir.model,name:pms.model_wizard_folio_changes +#: model_terms:ir.ui.view,arch_db:pms.wizard_folio_changes_view_form +msgid "Folio Changes" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Folio Form" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_account_move_line__folio_line_ids +msgid "Folio Lines" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__name +msgid "Folio Number" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_tree +msgid "Folio Pending Amount" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__folio_id +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__reference_folio_id +msgid "Folio Reference" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_folio_sale_line +msgid "Folio Sale Line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__folio_sequence_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__folio_sequence +msgid "Folio Sequence" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__state +msgid "Folio Status" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__booking_engine_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__booking_duplicate_id +msgid "Folio Wizard ID" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__note +msgid "Folio billing terms and conditions" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_booking_duplicate +#: model:ir.actions.act_window,name:pms.action_booking_engine +msgid "Folio creation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_engine__folio_id +msgid "Folio in which are included new reservations" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__folio_id +msgid "Folio in which the service is included" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__move_ids +msgid "Folio invoices related to account move." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__name +msgid "" +"Folio name. When creating a folio the name is automatically formed with a " +"sequence" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__payment_multi +msgid "Folio paid with payments assigned to other folios" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__invoice_lines +msgid "Folio sale line invoice lines" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__state +msgid "" +"Folio status; it can be Quotation, Quotation Sent, Confirmed, Locked or " +"Cancelled" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__reference_folio_id +msgid "Folio to copy data" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__folio_id +msgid "Folio to which folio sale line belongs" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__folio_id +msgid "Folio to which reservation of checkin partner belongs" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.pms_partner_folios +#: model:ir.model.fields,field_description:pms.field_account_bank_statement_line__folio_ids +#: model:ir.model.fields,field_description:pms.field_account_move__folio_ids +#: model:ir.model.fields,field_description:pms.field_account_move_line__folio_ids +#: model:ir.model.fields,field_description:pms.field_account_payment__folio_ids +#: model:ir.model.fields,field_description:pms.field_payment_transaction__folio_ids +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__created_folio_ids +#: model:ir.model.fields,field_description:pms.field_pms_property__pms_folio_ids +#: model:ir.model.fields,field_description:pms.field_res_partner__pms_folio_ids +#: model:ir.model.fields,field_description:pms.field_res_users__pms_folio_ids +#: model:ir.ui.menu,name:pms.pms_folio_menu +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_pivot +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folio +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folios +#: model_terms:ir.ui.view,arch_db:pms.portal_my_home_menu_folio +#: model_terms:ir.ui.view,arch_db:pms.res_partner_view_form +msgid "Folios" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__created_folio_ids +msgid "Folios already created" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.res_partner_view_form +msgid "Folios related with this contact" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_move__folio_ids +msgid "Folios where the account move are included" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_home_menu_folio +msgid "Folios/" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__message_follower_ids +#: model:ir.model.fields,field_description:pms.field_pms_folio__message_follower_ids +#: model:ir.model.fields,field_description:pms.field_pms_property__message_follower_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__message_follower_ids +#: model:ir.model.fields,field_description:pms.field_pms_room_type__message_follower_ids +msgid "Followers" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__message_channel_ids +#: model:ir.model.fields,field_description:pms.field_pms_folio__message_channel_ids +#: model:ir.model.fields,field_description:pms.field_pms_property__message_channel_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__message_channel_ids +#: model:ir.model.fields,field_description:pms.field_pms_room_type__message_channel_ids +msgid "Followers (Channels)" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__message_partner_ids +#: model:ir.model.fields,field_description:pms.field_pms_folio__message_partner_ids +#: model:ir.model.fields,field_description:pms.field_pms_property__message_partner_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__message_partner_ids +#: model:ir.model.fields,field_description:pms.field_pms_room_type__message_partner_ids +msgid "Followers (Partners)" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__activity_type_icon +#: model:ir.model.fields,help:pms.field_pms_folio__activity_type_icon +#: model:ir.model.fields,help:pms.field_pms_property__activity_type_icon +#: model:ir.model.fields,help:pms.field_pms_reservation__activity_type_icon +#: model:ir.model.fields,help:pms.field_pms_room_type__activity_type_icon +msgid "Font awesome icon e.g. fa-tasks" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_account_journal__allowed_pms_payments +msgid "For manual payments" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_service__no_auto_add_lines +msgid "Force No Auto Add Lines" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__force_nothing_to_invoice +msgid "Force no invoice" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__virtual_available +msgid "Forecast Quantity" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__virtual_available +msgid "" +"Forecast quantity (computed as Quantity On Hand - Outgoing + Incoming)\n" +"In a context with a single Stock Location, this includes goods stored in this location, or any of its children.\n" +"In a context with a single Warehouse, this includes goods stored in the Stock Location of this Warehouse, or any of its children.\n" +"Otherwise, this includes goods stored in any Stock Location with 'internal' type." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__free_qty +msgid "" +"Forecast quantity (computed as Quantity On Hand - reserved quantity)\n" +"In a context with a single Stock Location, this includes goods stored in this location, or any of its children.\n" +"In a context with a single Warehouse, this includes goods stored in the Stock Location of this Warehouse, or any of its children.\n" +"Otherwise, this includes goods stored in any Stock Location with 'internal' type." +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_property.py:0 +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Format Arrival Hour (HH:MM) Error: %s" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_property.py:0 +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Format Departure Hour (HH:MM) Error: %s" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__email_formatted +msgid "Format email address \"Name \"" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__email_formatted +msgid "Formatted Email" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room__short_name +msgid "" +"Four character name, if not set, autocompletes with the first two letters of" +" the room name and two incremental numbers" +msgstr "" + +#. module: pms +#: model:pms.ubication,name:pms.pms_ubication_4st_floor +msgid "Fourth floor" +msgstr "" + +#. module: pms +#: model:product.product,name:pms.pms_service_free_bar +#: model:product.template,name:pms.pms_service_free_bar_product_template +msgid "Free Bar" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__free_qty +msgid "Free To Use Quantity " +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.wizard_folio_changes_view_form +msgid "Friday" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__start_date +msgid "From" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__res_partner__invoicing_policy__checkout +msgid "From Checkout" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__start_date +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__start_date +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__checkin +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__checkin +msgid "From:" +msgstr "" + +#. module: pms +#: model:pms.board.service,name:pms.pms_board_service_full_board +msgid "Full Board" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__folio_sale_line__invoice_status__invoiced +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__invoice_status__invoiced +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__invoice_status__invoiced +#: model:ir.model.fields.selection,name:pms.selection__pms_service__invoice_status__invoiced +msgid "Fully Invoiced" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +msgid "Future" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__gender +#: model:ir.model.fields,field_description:pms.field_pms_property__gender +#: model:ir.model.fields,field_description:pms.field_res_partner__gender +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "Gender" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Gender *" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "General Info" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "General Information" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan_rule__quota +#: model:ir.model.fields,help:pms.field_pms_massive_changes_wizard__quota +msgid "Generic Quota assigned." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__partner_latitude +msgid "Geo Latitude" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__partner_longitude +msgid "Geo Longitude" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_folio_view_tree +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_reservation_view_tree +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_tree +msgid "Get in" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__board_price +#: model:ir.model.fields,help:pms.field_product_product__board_price +msgid "Get price on board service" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_line__price_day_total +msgid "Get the price with discount applied" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__packaging_ids +msgid "Gives the different ways to package the same product." +msgstr "" + +#. module: pms +#: model:pms.room.type,name:pms.demo_pms_room_type_grand_suite +#: model:product.product,name:pms.demo_pms_room_type_grand_suite_product_product +msgid "Grand suite" +msgstr "" + +#. module: pms +#: model:pms.ubication,name:pms.pms_ubication_ground_floor +msgid "Ground floor" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "Group By" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_amenity_search +#: model_terms:ir.ui.view,arch_db:pms.pms_service_view_search +msgid "Group By..." +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folio_invitations +msgid "Guest" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__partner_requests +msgid "Guest requests" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.folio_portal_template +msgid "Guests to Precheckin" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__checkin_partner_ids +msgid "Guests who will occupy the room" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__default_arrival_hour +#: model:ir.model.fields,help:pms.field_pms_property__default_departure_hour +msgid "HH:mm Format" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_ir_http +msgid "HTTP Routing" +msgstr "" + +#. module: pms +#: model:pms.amenity,name:pms.pms_amenity_hair_dryer +msgid "Hair Dryer" +msgstr "" + +#. module: pms +#: model:pms.board.service,name:pms.pms_board_service_half_board +msgid "Half Board" +msgstr "" + +#. module: pms +#: model:pms.amenity,name:pms.pms_amenity_half_sized_refrigerator +msgid "Half-sized Refrigerator" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__show_update_pricelist +msgid "Has Pricelist Changed" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__has_unreconciled_entries +msgid "Has Unreconciled Entries" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__has_down_payments +#: model:ir.model.fields,help:pms.field_folio_advance_payment_inv__has_down_payments +msgid "Has down payments" +msgstr "" + +#. module: pms +#: model:mail.template,subject:pms.precheckin_invitation_email +msgid "" +"Hi ${object.firstname}, do your check-in now in " +"${object.pms_property_id.name}" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation_precheckin +msgid "Hi," +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +msgid "Hide Strangers" +msgstr "" + +#. module: pms +#: model:pms.amenity,name:pms.pms_amenity_high_speed_wired_internet_connection +msgid "High speed Wired Internet access" +msgstr "" + +#. module: pms +#: model:pms.amenity,name:pms.pms_amenity_high_quality_shampoo_and_sopa_essential_herbs +msgid "High-quality Shampoo and Soap Essential Herbs" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.folio_portal_template +msgid "History" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_prechekin_reservation +msgid "Host" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__document_number +msgid "Host document number" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_kanban_view +msgid "Hosted's Name" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_graph +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_pivot +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_calendar +msgid "Hosts" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_account_analytic_distribution__id +#: model:ir.model.fields,field_description:pms.field_account_analytic_line__id +#: model:ir.model.fields,field_description:pms.field_account_bank_statement__id +#: model:ir.model.fields,field_description:pms.field_account_bank_statement_line__id +#: model:ir.model.fields,field_description:pms.field_account_journal__id +#: model:ir.model.fields,field_description:pms.field_account_move__id +#: model:ir.model.fields,field_description:pms.field_account_move_line__id +#: model:ir.model.fields,field_description:pms.field_account_payment__id +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__id +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__id +#: model:ir.model.fields,field_description:pms.field_ir_config_parameter__id +#: model:ir.model.fields,field_description:pms.field_ir_http__id +#: model:ir.model.fields,field_description:pms.field_ir_pms_property__id +#: model:ir.model.fields,field_description:pms.field_mail_compose_message__id +#: model:ir.model.fields,field_description:pms.field_payment_acquirer__id +#: model:ir.model.fields,field_description:pms.field_payment_transaction__id +#: model:ir.model.fields,field_description:pms.field_pms_advanced_filters_wizard__id +#: model:ir.model.fields,field_description:pms.field_pms_amenity__id +#: model:ir.model.fields,field_description:pms.field_pms_amenity_type__id +#: model:ir.model.fields,field_description:pms.field_pms_automated_mails__id +#: model:ir.model.fields,field_description:pms.field_pms_availability__id +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan__id +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__id +#: model:ir.model.fields,field_description:pms.field_pms_board_service__id +#: model:ir.model.fields,field_description:pms.field_pms_board_service_line__id +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type__id +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type_line__id +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__id +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__id +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__id +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__id +#: model:ir.model.fields,field_description:pms.field_pms_folio__id +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__id +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__id +#: model:ir.model.fields,field_description:pms.field_pms_num_rooms_selection__id +#: model:ir.model.fields,field_description:pms.field_pms_property__id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__id +#: model:ir.model.fields,field_description:pms.field_pms_room__id +#: model:ir.model.fields,field_description:pms.field_pms_room_type__id +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class__id +#: model:ir.model.fields,field_description:pms.field_pms_sale_channel__id +#: model:ir.model.fields,field_description:pms.field_pms_service__id +#: model:ir.model.fields,field_description:pms.field_pms_service_line__id +#: model:ir.model.fields,field_description:pms.field_pms_several_partners_wizard__id +#: model:ir.model.fields,field_description:pms.field_pms_team_member__id +#: model:ir.model.fields,field_description:pms.field_pms_ubication__id +#: model:ir.model.fields,field_description:pms.field_pms_wizard_reservation_lines_split__id +#: model:ir.model.fields,field_description:pms.field_product_pricelist__id +#: model:ir.model.fields,field_description:pms.field_product_pricelist_item__id +#: model:ir.model.fields,field_description:pms.field_product_product__id +#: model:ir.model.fields,field_description:pms.field_product_template__id +#: model:ir.model.fields,field_description:pms.field_res_company__id +#: model:ir.model.fields,field_description:pms.field_res_country__id +#: model:ir.model.fields,field_description:pms.field_res_partner__id +#: model:ir.model.fields,field_description:pms.field_res_partner_category__id +#: model:ir.model.fields,field_description:pms.field_res_partner_id_category__id +#: model:ir.model.fields,field_description:pms.field_res_partner_id_number__id +#: model:ir.model.fields,field_description:pms.field_res_users__id +#: model:ir.model.fields,field_description:pms.field_room_closure_reason__id +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__id +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__id +msgid "ID" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_res_partner_id_number__name +msgid "ID Number" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_res_partner_id_number__category_id +msgid "ID type defined in configuration. For example, Driver License" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__im_status +msgid "IM Status" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__activity_exception_icon +#: model:ir.model.fields,field_description:pms.field_pms_folio__activity_exception_icon +#: model:ir.model.fields,field_description:pms.field_pms_property__activity_exception_icon +#: model:ir.model.fields,field_description:pms.field_pms_reservation__activity_exception_icon +#: model:ir.model.fields,field_description:pms.field_pms_room_type__activity_exception_icon +msgid "Icon" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__activity_exception_icon +#: model:ir.model.fields,help:pms.field_pms_folio__activity_exception_icon +#: model:ir.model.fields,help:pms.field_pms_property__activity_exception_icon +#: model:ir.model.fields,help:pms.field_pms_reservation__activity_exception_icon +#: model:ir.model.fields,help:pms.field_pms_room_type__activity_exception_icon +msgid "Icon to indicate an exception activity." +msgstr "" + +#. module: pms +#: model:res.partner.id_category,name:pms.document_type_identification_document +msgid "Identification Document" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__id_numbers +msgid "Identification Numbers" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__default_code +msgid "Identification code for a room type" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__aeat_identification +msgid "Identification for AEAT purposes" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__identifier +msgid "Identifier" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "" +"If a Folio is done, you cannot modify it manually anymore. However, you will" +" still be able to invoice. This is used to freeze the Folio." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__message_needaction +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__message_unread +#: model:ir.model.fields,help:pms.field_pms_folio__message_needaction +#: model:ir.model.fields,help:pms.field_pms_folio__message_unread +#: model:ir.model.fields,help:pms.field_pms_property__message_needaction +#: model:ir.model.fields,help:pms.field_pms_property__message_unread +#: model:ir.model.fields,help:pms.field_pms_reservation__message_needaction +#: model:ir.model.fields,help:pms.field_pms_reservation__message_unread +#: model:ir.model.fields,help:pms.field_pms_room_type__message_needaction +#: model:ir.model.fields,help:pms.field_pms_room_type__message_unread +msgid "If checked, new messages require your attention." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__message_has_error +#: model:ir.model.fields,help:pms.field_pms_folio__message_has_error +#: model:ir.model.fields,help:pms.field_pms_property__message_has_error +#: model:ir.model.fields,help:pms.field_pms_reservation__message_has_error +#: model:ir.model.fields,help:pms.field_pms_room_type__message_has_error +msgid "If checked, some messages have a delivery error." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_wizard_folio_changes__apply_day_qty +msgid "If not set, it will use the default product day qty" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__team_id +msgid "" +"If set, this Sales Team will be used for sales and assignments related to " +"this partner" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__is_blacklisted +msgid "" +"If the email address is on the blacklist, the contact won't receive mass " +"mailing anymore, from any list" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_product_pricelist__is_pms_available +msgid "If the pricelist is available in the PMS" +msgstr "" + +#. module: pms +#: code:addons/pms/models/product_pricelist.py:0 +#, python-format +msgid "" +"If the pricelist is available in the PMS, you must select an availability " +"plan" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_res_company__document_partner_required +msgid "" +"If true, the partner document is required\n" +" to create a new contact" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan__active +msgid "" +"If unchecked, it will allow you to hide the Availability plan without " +"removing it." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__active +msgid "" +"If unchecked, it will allow you to hide the product without removing it." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type_class__active +msgid "If unchecked, it will allow you to hide the room type" +msgstr "" + +#. module: pms +#: code:addons/pms/controllers/pms_portal.py:0 +#, python-format +msgid "" +"If we store your payment information on our server, subscription payments " +"will be made automatically." +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation_precheckin +msgid "" +"If you wish, you can share with the rest of the guests the access to their " +"check-in so that they can fill it out.
" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__image_128 +#: model:ir.model.fields,field_description:pms.field_pms_property__image_1920 +#: model:ir.model.fields,field_description:pms.field_pms_room_type__image_1920 +msgid "Image" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__image_1024 +#: model:ir.model.fields,field_description:pms.field_pms_room_type__image_1024 +msgid "Image 1024" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__image_128 +#: model:ir.model.fields,field_description:pms.field_pms_room_type__image_128 +msgid "Image 128" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__image_256 +#: model:ir.model.fields,field_description:pms.field_pms_room_type__image_256 +msgid "Image 256" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__image_512 +#: model:ir.model.fields,field_description:pms.field_pms_room_type__image_512 +msgid "Image 512" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__logo +msgid "Image in checkin" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__product_image +msgid "Image of the service" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__standard_price +msgid "" +"In Standard Price & AVCO: value of the product (automatically computed in AVCO).\n" +" In FIFO: value of the next unit that will leave the stock (automatically computed).\n" +" Used to value the product when the purchase cost is not known (e.g. inventory adjustment).\n" +" Used to compute margins on sale orders." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__partner_name +msgid "In the name of whom the reservation is made" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__cancelled_reason__intime +msgid "In time" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__partner_name +#: model:ir.model.fields,help:pms.field_pms_booking_engine__partner_name +msgid "In whose name is the reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__service_ids +msgid "Included services in the reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__deposit_account_id +#: model:ir.model.fields,field_description:pms.field_pms_room_type__property_account_income_id +msgid "Income Account" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__incoming_qty +msgid "Incoming" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_checkin_partner__state__draft +msgid "Incomplete data" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__overbooking +msgid "Indicate if exists overbooking" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_line__overbooking +msgid "Indicate if exists overbooking in the reservation line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__is_reselling +msgid "Indicate if exists reselling in any reservation line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan_rule__closed +msgid "Indicate if property is closed or not" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__cancelled_reason +msgid "Indicates cause of cancelled" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__daily_limit +#: model:ir.model.fields,help:pms.field_product_product__daily_limit +#: model:ir.model.fields,help:pms.field_product_template__daily_limit +msgid "Indicates how much products can consumed in one day" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__invoice_to_agency +#: model:ir.model.fields,help:pms.field_res_partner__invoice_to_agency +#: model:ir.model.fields,help:pms.field_res_users__invoice_to_agency +msgid "Indicates if agency invoices partner" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__invoice_to_agency +msgid "" +"Indicates if agency invoices partner\n" +" (it only affects those nights/services sold through the agency)" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__apply_pricelist +#: model:ir.model.fields,help:pms.field_res_partner__apply_pricelist +#: model:ir.model.fields,help:pms.field_res_users__apply_pricelist +msgid "Indicates if agency pricelist is applied to his reservations" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__per_day +msgid "Indicates if service is sold by days" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__is_crib +#: model:ir.model.fields,help:pms.field_product_product__is_crib +#: model:ir.model.fields,help:pms.field_product_template__is_crib +msgid "Indicates if that product is a crib" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__is_extra_bed +#: model:ir.model.fields,help:pms.field_product_product__is_extra_bed +#: model:ir.model.fields,help:pms.field_product_template__is_extra_bed +msgid "Indicates if that product is a extra bed, add +1 capacity in the room" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__is_pms_available +#: model:ir.model.fields,help:pms.field_product_product__is_pms_available +#: model:ir.model.fields,help:pms.field_product_template__is_pms_available +msgid "Indicates if that product is available in PMS" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__is_agency +#: model:ir.model.fields,help:pms.field_res_partner__is_agency +#: model:ir.model.fields,help:pms.field_res_users__is_agency +msgid "Indicates if the partner is an agency" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__blocked +msgid "Indicates if the reservation is blocked" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_line__is_reselling +msgid "Indicates if the reservation line is reselling" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_sale_channel__is_on_line +msgid "Indicates if the sale channel is on-line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__is_board_service +msgid "" +"Indicates if the service included in folio sale line is part of a board " +"service" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__is_cancel_penalty +msgid "Indicates if the service is a cancel penalty" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__is_board_service +msgid "Indicates if the service is part of a board service" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service_line__is_board_service +msgid "Indicates if the service line is part of a board service" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service_room_type__by_default +msgid "Indicates if this board service is applied by default in the room type" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__per_day +#: model:ir.model.fields,help:pms.field_product_product__per_day +#: model:ir.model.fields,help:pms.field_product_template__per_day +msgid "Indicates that the product is sold by days" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__per_person +#: model:ir.model.fields,help:pms.field_product_product__per_person +#: model:ir.model.fields,help:pms.field_product_template__per_person +msgid "Indicates that the product is sold per person" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_wizard_folio_changes__dates_incongruence +msgid "" +"Indicates that there are reservations with different checkin and/or checkout" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room__parent_id +msgid "Indicates that this room is a child of another room" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__out_service_description +#: model:ir.model.fields,help:pms.field_pms_reservation__out_service_description +msgid "Indicates the cause of out of service" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__default_invoice_to +#: model:ir.model.fields,help:pms.field_pms_reservation_line__default_invoice_to +#: model:ir.model.fields,help:pms.field_pms_service__default_invoice_to +#: model:ir.model.fields,help:pms.field_pms_service_line__default_invoice_to +msgid "" +"Indicates the contact to which this line will be\n" +" billed by default, if it is not established,\n" +" a guest or the generic contact will be used instead" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__ready_for_checkin +msgid "" +"Indicates the reservations with checkin_partner data enought to checkin" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__consumed_on +#: model:ir.model.fields,help:pms.field_product_product__consumed_on +#: model:ir.model.fields,help:pms.field_product_template__consumed_on +msgid "Indicates when the product is consumed" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_sale_channel__channel_type__indirect +msgid "Indirect" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__industry_id +msgid "Industry" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_amenity_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_form +msgid "Information" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_ir_pms_property__value_integer +msgid "Integer Field Value" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_board_service.py:0 +#, python-format +msgid "" +"Integrity error: There's multiple board services with the same code %s and " +"properties" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_room_type.py:0 +#: code:addons/pms/models/pms_room_type_class.py:0 +#, python-format +msgid "" +"Integrity error: There's multiple room types with the same code %s and " +"properties" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__internal_comment +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__internal_comment +#: model:ir.model.fields,field_description:pms.field_pms_folio__internal_comment +#: model:ir.model.fields,field_description:pms.field_pms_reservation__folio_internal_comment +msgid "Internal Folio Notes" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__internal_comment +#: model:ir.model.fields,help:pms.field_pms_booking_engine__internal_comment +#: model:ir.model.fields,help:pms.field_pms_folio__internal_comment +msgid "Internal Folio notes for Staff" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__partner_internal_comment +#: model:ir.model.fields,field_description:pms.field_pms_reservation__partner_internal_comment +msgid "Internal Partner Notes" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_amenity__default_code +msgid "Internal Reference" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.booking_duplicate +#: model_terms:ir.ui.view,arch_db:pms.booking_engine +msgid "Internal comment Folio" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__folio_internal_comment +msgid "Internal comment for folio" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__check_adults +msgid "Internal field to force room capacity validations" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__partner_internal_comment +#: model:ir.model.fields,help:pms.field_pms_reservation__partner_internal_comment +msgid "Internal notes of the partner" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_amenity__default_code +msgid "Internal unique identifier of the amenity" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__barcode +msgid "International Article Number used for product identification." +msgstr "" + +#. module: pms +#: code:addons/pms/wizards/wizard_split_join_swap_reservation.py:0 +#, python-format +msgid "Invalid date for reservation line " +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Invalid domain operator %s for left of cancel" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Invalid domain operator %s for left of checkin" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Invalid domain operator %s for left of checkout" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Invalid domain right operand %s for left of cancel" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Invalid domain right operand %s for left of checkin" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Invalid domain right operand %s for left of checkout" +msgstr "" + +#. module: pms +#: code:addons/pms/wizards/wizard_split_join_swap_reservation.py:0 +#, python-format +msgid "Invalid reservation" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "Invalid token found! Token acquirer %s != %s" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "Invalid token found! Token partner %s != %s" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__property_stock_inventory +msgid "Inventory Location" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__valuation +msgid "Inventory Valuation" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folio_invitations +msgid "Invitation email sent" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__invoice_warn +#: model_terms:ir.ui.view,arch_db:pms.report_invoice_document +msgid "Invoice" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__type +msgid "" +"Invoice & Delivery addresses are used in sales orders. Private addresses are" +" only visible by authorized users." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__invoice_to_agency +#: model:ir.model.fields,field_description:pms.field_pms_property__invoice_to_agency +#: model:ir.model.fields,field_description:pms.field_res_partner__invoice_to_agency +#: model:ir.model.fields,field_description:pms.field_res_users__invoice_to_agency +msgid "Invoice Agency" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__invoice_count +msgid "Invoice Count" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.view_folio_advance_payment_inv +msgid "Invoice Folio Order" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__invoice_lines +msgid "Invoice Lines" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__invoice_status +#: model:ir.model.fields,field_description:pms.field_pms_folio__invoice_status +#: model:ir.model.fields,field_description:pms.field_pms_reservation__invoice_status +#: model:ir.model.fields,field_description:pms.field_pms_service__invoice_status +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +msgid "Invoice Status" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__invoice_status +msgid "Invoice Status; it can be: invoiced, to invoice, no" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__invoice_status +msgid "Invoice Status; it can be: invoiced, to invoice, to confirm, no" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__partner_invoice_ids +msgid "Invoice address for current group." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_advance_payment_inv__partner_invoice_id +msgid "Invoice address for current partner" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__default_invoice_to +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__default_invoice_to +#: model:ir.model.fields,field_description:pms.field_pms_service__default_invoice_to +#: model:ir.model.fields,field_description:pms.field_pms_service_line__default_invoice_to +msgid "Invoice to" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Invoiced" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__qty_invoiced +msgid "Invoiced Quantity" +msgstr "" + +#. module: pms +#: code:addons/pms/models/folio_sale_line.py:0 +#, python-format +msgid "Invoiced Quantity: %s" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__move_ids +#: model:ir.model.fields,field_description:pms.field_pms_property__invoice_ids +#: model:ir.ui.menu,name:pms.pms_invoice_menu +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Invoices" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.view_folio_advance_payment_inv +msgid "" +"Invoices will be created in draft so that you can review\n" +" them before validation." +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Invoicing" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__invoicing_month_day +#: model:ir.model.fields,field_description:pms.field_res_partner__invoicing_month_day +#: model:ir.model.fields,field_description:pms.field_res_users__invoicing_month_day +msgid "Invoicing Month Day" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__invoicing_policy +#: model:ir.model.fields,field_description:pms.field_pms_room_type__invoice_policy +#: model:ir.model.fields,field_description:pms.field_res_partner__invoicing_policy +#: model:ir.model.fields,field_description:pms.field_res_users__invoicing_policy +msgid "Invoicing Policy" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_ir_pms_property +msgid "IrPmsProperty" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__is_agency +#: model:ir.model.fields,field_description:pms.field_res_partner__is_agency +#: model:ir.model.fields,field_description:pms.field_res_users__is_agency +msgid "Is Agency" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_service__is_board_service +#: model:ir.model.fields,field_description:pms.field_pms_service_line__is_board_service +msgid "Is Board Service" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_service__is_cancel_penalty +msgid "Is Cancel Penalty" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__message_is_follower +#: model:ir.model.fields,field_description:pms.field_pms_folio__message_is_follower +#: model:ir.model.fields,field_description:pms.field_pms_property__message_is_follower +#: model:ir.model.fields,field_description:pms.field_pms_reservation__message_is_follower +#: model:ir.model.fields,field_description:pms.field_pms_room_type__message_is_follower +msgid "Is Follower" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__overbooking +msgid "Is Overbooking" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__is_product_variant +msgid "Is Product Variant" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__is_company +msgid "Is a Company" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room__is_shared_room +msgid "Is a Shared Room" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__is_crib +#: model:ir.model.fields,field_description:pms.field_product_product__is_crib +#: model:ir.model.fields,field_description:pms.field_product_template__is_crib +msgid "Is a baby crib" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__has_configurable_attributes +msgid "Is a configurable product" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__is_downpayment +msgid "Is a down payment" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__is_pms_available +#: model:ir.model.fields,field_description:pms.field_product_product__is_pms_available +#: model:ir.model.fields,field_description:pms.field_product_template__is_pms_available +msgid "Is available in PMS" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__is_extra_bed +#: model:ir.model.fields,field_description:pms.field_product_product__is_extra_bed +#: model:ir.model.fields,field_description:pms.field_product_template__is_extra_bed +msgid "Is extra bed" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_checkin_partner.py:0 +#, python-format +msgid "Is mandatory indicate the reservation on the checkin" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_checkin_partner.py:0 +#, python-format +msgid "Is not possible to create the proposed check-in in this reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_cancelation_rule__days_late +msgid "" +"Is number of days late in the cancelation rule if the value of the " +"apply_on_late field is specify days." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_cancelation_rule__days_noshow +msgid "" +"Is number of days no show in the cancelation rule if the value of the " +"apply_on_show field is specify days." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_wizard_reservation_lines_split__allowed_room_ids +msgid "It contains all available rooms for this line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__allowed_room_ids +#: model:ir.model.fields,help:pms.field_pms_reservation_duplicate__allowed_room_ids +msgid "It contains all available rooms for this reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__to_assign +msgid "" +"It is True if the room of the reservation has been assigned automatically, " +"False if it was confirmed by a person in charge" +msgstr "" + +#. module: pms +#: code:addons/pms/models/folio_sale_line.py:0 +#, python-format +msgid "" +"It is forbidden to modify the following fields\n" +" in a locked folio (fields already invoiced):\n" +"%s" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_checkin_partner.py:0 +#, python-format +msgid "It is not yet checkin day!" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__qty_invoiced +msgid "It is the amount invoiced when an invoice is issued" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__checkin +msgid "It is the checkin date of the reservation, " +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__checkout +msgid "It is the checkout date of the reservation, " +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_line__reservation_id +msgid "It is the reservation in a reservation line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__preferred_room_id +msgid "" +"It's the preferred room assigned to reservation, empty if reservation is " +"splitted" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_product_pricelist__item_ids +msgid "Items" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_product_pricelist__item_ids +msgid "Items for which the pricelist is made up" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_checkin_partner.py:0 +#, python-format +msgid "Its too late to checkin" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__function +msgid "Job Position" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation_split_join_swap_wizard__operation__join +#: model_terms:ir.ui.view,arch_db:pms.reservation_wizard +msgid "Join reservation" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Join the reservation" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_account_journal +#: model:ir.model.fields,field_description:pms.field_account_bank_statement__journal_id +msgid "Journal" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_account_move +#: model:ir.model.fields,field_description:pms.field_account_move_line__move_id +msgid "Journal Entry" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_account_move_line +msgid "Journal Item" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__journal_item_count +msgid "Journal Items" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__journal_normal_invoice_id +msgid "Journal used to create the normal invoice" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__journal_simplified_invoice_id +msgid "Journal used to create the simplified invoice" +msgstr "" + +#. module: pms +#: model:pms.room.type,name:pms.demo_pms_room_type_junior_suite +#: model:product.product,name:pms.demo_pms_room_type_junior_suite_product_product +msgid "Junior suite" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__property_account_income_id +msgid "" +"Keep this field empty to use the default value from the product category." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__property_account_expense_id +msgid "" +"Keep this field empty to use the default value from the product category. If" +" anglo-saxon accounting with automated valuation method is configured, the " +"expense account on the product category will be used." +msgstr "" + +#. module: pms +#: model:pms.amenity.type,name:pms.pms_amenity_type_kitchen_facilities +msgid "Kitchen facilities" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_account_move_line__name +msgid "Label" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__lang +#: model:ir.model.fields,field_description:pms.field_pms_property__lang +#: model:ir.model.fields,field_description:pms.field_pms_reservation__lang +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Language" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__lang +msgid "Language used for the folio" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__last_checkout +msgid "Last Folio Checkout" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_account_analytic_distribution____last_update +#: model:ir.model.fields,field_description:pms.field_account_analytic_line____last_update +#: model:ir.model.fields,field_description:pms.field_account_bank_statement____last_update +#: model:ir.model.fields,field_description:pms.field_account_bank_statement_line____last_update +#: model:ir.model.fields,field_description:pms.field_account_journal____last_update +#: model:ir.model.fields,field_description:pms.field_account_move____last_update +#: model:ir.model.fields,field_description:pms.field_account_move_line____last_update +#: model:ir.model.fields,field_description:pms.field_account_payment____last_update +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv____last_update +#: model:ir.model.fields,field_description:pms.field_folio_sale_line____last_update +#: model:ir.model.fields,field_description:pms.field_ir_config_parameter____last_update +#: model:ir.model.fields,field_description:pms.field_ir_http____last_update +#: model:ir.model.fields,field_description:pms.field_ir_pms_property____last_update +#: model:ir.model.fields,field_description:pms.field_mail_compose_message____last_update +#: model:ir.model.fields,field_description:pms.field_payment_acquirer____last_update +#: model:ir.model.fields,field_description:pms.field_payment_transaction____last_update +#: model:ir.model.fields,field_description:pms.field_pms_advanced_filters_wizard____last_update +#: model:ir.model.fields,field_description:pms.field_pms_amenity____last_update +#: model:ir.model.fields,field_description:pms.field_pms_amenity_type____last_update +#: model:ir.model.fields,field_description:pms.field_pms_automated_mails____last_update +#: model:ir.model.fields,field_description:pms.field_pms_availability____last_update +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan____last_update +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule____last_update +#: model:ir.model.fields,field_description:pms.field_pms_board_service____last_update +#: model:ir.model.fields,field_description:pms.field_pms_board_service_line____last_update +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type____last_update +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type_line____last_update +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate____last_update +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine____last_update +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule____last_update +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner____last_update +#: model:ir.model.fields,field_description:pms.field_pms_folio____last_update +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard____last_update +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard____last_update +#: model:ir.model.fields,field_description:pms.field_pms_num_rooms_selection____last_update +#: model:ir.model.fields,field_description:pms.field_pms_property____last_update +#: model:ir.model.fields,field_description:pms.field_pms_reservation____last_update +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate____last_update +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line____last_update +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard____last_update +#: model:ir.model.fields,field_description:pms.field_pms_room____last_update +#: model:ir.model.fields,field_description:pms.field_pms_room_type____last_update +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class____last_update +#: model:ir.model.fields,field_description:pms.field_pms_sale_channel____last_update +#: model:ir.model.fields,field_description:pms.field_pms_service____last_update +#: model:ir.model.fields,field_description:pms.field_pms_service_line____last_update +#: model:ir.model.fields,field_description:pms.field_pms_several_partners_wizard____last_update +#: model:ir.model.fields,field_description:pms.field_pms_team_member____last_update +#: model:ir.model.fields,field_description:pms.field_pms_ubication____last_update +#: model:ir.model.fields,field_description:pms.field_pms_wizard_reservation_lines_split____last_update +#: model:ir.model.fields,field_description:pms.field_product_pricelist____last_update +#: model:ir.model.fields,field_description:pms.field_product_pricelist_item____last_update +#: model:ir.model.fields,field_description:pms.field_product_product____last_update +#: model:ir.model.fields,field_description:pms.field_product_template____last_update +#: model:ir.model.fields,field_description:pms.field_res_company____last_update +#: model:ir.model.fields,field_description:pms.field_res_country____last_update +#: model:ir.model.fields,field_description:pms.field_res_partner____last_update +#: model:ir.model.fields,field_description:pms.field_res_partner_category____last_update +#: model:ir.model.fields,field_description:pms.field_res_partner_id_category____last_update +#: model:ir.model.fields,field_description:pms.field_res_partner_id_number____last_update +#: model:ir.model.fields,field_description:pms.field_res_users____last_update +#: model:ir.model.fields,field_description:pms.field_room_closure_reason____last_update +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes____last_update +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio____last_update +msgid "Last Modified on" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__lastname +msgid "Last Name" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__write_uid +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__write_uid +#: model:ir.model.fields,field_description:pms.field_ir_pms_property__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_advanced_filters_wizard__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_amenity__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_amenity_type__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_automated_mails__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_availability__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_board_service__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_board_service_line__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type_line__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_folio__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_num_rooms_selection__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_property__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_reservation__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_room__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_room_type__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_sale_channel__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_service__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_service_line__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_several_partners_wizard__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_team_member__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_ubication__write_uid +#: model:ir.model.fields,field_description:pms.field_pms_wizard_reservation_lines_split__write_uid +#: model:ir.model.fields,field_description:pms.field_room_closure_reason__write_uid +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__write_uid +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__write_date +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__write_date +#: model:ir.model.fields,field_description:pms.field_ir_pms_property__write_date +#: model:ir.model.fields,field_description:pms.field_pms_advanced_filters_wizard__write_date +#: model:ir.model.fields,field_description:pms.field_pms_amenity__write_date +#: model:ir.model.fields,field_description:pms.field_pms_amenity_type__write_date +#: model:ir.model.fields,field_description:pms.field_pms_automated_mails__write_date +#: model:ir.model.fields,field_description:pms.field_pms_availability__write_date +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan__write_date +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__write_date +#: model:ir.model.fields,field_description:pms.field_pms_board_service__write_date +#: model:ir.model.fields,field_description:pms.field_pms_board_service_line__write_date +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type_line__write_date +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__write_date +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__write_date +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__write_date +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__write_date +#: model:ir.model.fields,field_description:pms.field_pms_folio__write_date +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__write_date +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__write_date +#: model:ir.model.fields,field_description:pms.field_pms_num_rooms_selection__write_date +#: model:ir.model.fields,field_description:pms.field_pms_property__write_date +#: model:ir.model.fields,field_description:pms.field_pms_reservation__write_date +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__write_date +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__write_date +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__write_date +#: model:ir.model.fields,field_description:pms.field_pms_room__write_date +#: model:ir.model.fields,field_description:pms.field_pms_room_type__write_date +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class__write_date +#: model:ir.model.fields,field_description:pms.field_pms_sale_channel__write_date +#: model:ir.model.fields,field_description:pms.field_pms_service__write_date +#: model:ir.model.fields,field_description:pms.field_pms_service_line__write_date +#: model:ir.model.fields,field_description:pms.field_pms_several_partners_wizard__write_date +#: model:ir.model.fields,field_description:pms.field_pms_team_member__write_date +#: model:ir.model.fields,field_description:pms.field_pms_ubication__write_date +#: model:ir.model.fields,field_description:pms.field_pms_wizard_reservation_lines_split__write_date +#: model:ir.model.fields,field_description:pms.field_room_closure_reason__write_date +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__write_date +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__write_date +msgid "Last Updated on" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__lastname +#: model:ir.model.fields,field_description:pms.field_res_partner__lastname +#: model:ir.model.fields,field_description:pms.field_res_users__lastname +msgid "Last name" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__last_time_entries_checked +msgid "" +"Last time the invoices & payments matching was performed for this partner. " +"It is set either if there's not at least an unreconciled debit and an " +"unreconciled credit or if you click the \"Done\" button." +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Lastname *" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "Lastnames" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__cancelled_reason__late +#: model_terms:ir.ui.view,arch_db:pms.pms_cancelation_rule_form +msgid "Late" +msgstr "" + +#. module: pms +#: model:product.product,name:pms.pms_service_late_checkout +#: model:product.template,name:pms.pms_service_late_checkout_product_template +msgid "Late Check-out" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Late Payment" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__apply_on_late +msgid "Late apply on" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__days_late +msgid "Late first days" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Lates and NoShows" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__last_time_entries_checked +msgid "Latest Invoices & Payments Matching Date" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__recompute_prices +msgid "" +"Leave unchecked if you want to respect\n" +" the price of the original reservation regardless\n" +" of what is marked in the rate" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_wizard_reservation_lines_split +msgid "Lines available to split" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__adults +msgid "List of adults there in guest list" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room__room_amenity_ids +msgid "List of amenities included in room" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__room_amenity_ids +msgid "List of amenities included in room type" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__location_id +msgid "Location" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__state__done +msgid "Locked" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_sale_channel__icon +msgid "Logo" +msgstr "" + +#. module: pms +#: model:product.product,name:pms.pms_service_lunch +#: model:product.template,name:pms.pms_service_lunch_product_template +msgid "Lunch" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__mail_information +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Mail Information" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__privacy_policy +msgid "Mail privacy policy " +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__message_main_attachment_id +#: model:ir.model.fields,field_description:pms.field_pms_folio__message_main_attachment_id +#: model:ir.model.fields,field_description:pms.field_pms_property__message_main_attachment_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__message_main_attachment_id +#: model:ir.model.fields,field_description:pms.field_pms_room_type__message_main_attachment_id +msgid "Main Attachment" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__manager_user_id +msgid "Main Manager" +msgstr "" + +#. module: pms +#: model:room.closure.reason,name:pms.pms_room_closure_reason_maintenance +msgid "Maintenance" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_checkin_partner__gender__male +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Male" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folio_invitations +msgid "Mandatory Firstname" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_property__default_invoicing_policy__manual +#: model:ir.model.fields.selection,name:pms.selection__res_company__pms_invoice_downpayment_policy__no +#: model:ir.model.fields.selection,name:pms.selection__res_partner__invoice_to_agency__manual +#: model:ir.model.fields.selection,name:pms.selection__res_partner__invoicing_policy__manual +msgid "Manual" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__valuation +msgid "" +"Manual: The accounting entries to value the inventory are not posted automatically.\n" +" Automated: An accounting entry is automatically created to value the inventory when a product enters or leaves the company.\n" +" " +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__service_type +msgid "" +"Manually set quantities on order: Invoice based on the manually entered quantity, without creating an analytic account.\n" +"Timesheets on contract: Invoice based on the tracked hours on the related timesheet.\n" +"Create a task and track hours: Create a task on the sales order validation and track the work hours." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__margin_days_autoinvoice +msgid "Margin Days" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__prepaid_warning_days +msgid "" +"Margin in days to create a notice if a payment advance has " +"not been recorded" +msgstr "" + +#. module: pms +#: model:ir.ui.menu,name:pms.menu_pms_room_massive_changes_wizard +msgid "Massive Changes" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.availability_view_form +#: model_terms:ir.ui.view,arch_db:pms.product_pricelist_view_form +msgid "Massive changes" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_wizard_massive_changes +msgid "Massive changes on Pricelist & Availability Plans" +msgstr "" + +#. module: pms +#: code:addons/pms/wizards/wizard_massive_changes.py:0 +#, python-format +msgid "Massive changes on Pricelist and Availability Plans" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__max_amount_simplified_invoice +msgid "Max Amount Simplified Invoice" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__max_reservation_priority +#: model:ir.model.fields,help:pms.field_pms_folio__max_reservation_priority +msgid "Max reservation priority on the entire folio" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__max_avail +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__max_avail +msgid "Max. Availability" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__max_stay +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__max_stay +msgid "Max. Stay" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__max_stay_arrival +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__max_stay_arrival +msgid "Max. Stay Arrival" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_availability_plan_rule.py:0 +#, python-format +msgid "Max. Stay Arrival can't be less than Min. Stay Arrival" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_availability_plan_rule.py:0 +#, python-format +msgid "Max. Stay Arrival can't be less than zero" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_availability_plan_rule.py:0 +#, python-format +msgid "Max. Stay can't be less than Min. Stay" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_availability_plan_rule.py:0 +#, python-format +msgid "Max. Stay can't be less than zero" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_cancelation_rule_form +msgid "Max. days InTime before Checkin" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__max_amount_simplified_invoice +msgid "Maximum amount to create the simplified invoice" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_cancelation_rule__days_intime +msgid "Maximum number of days for free cancellation before Checkin" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan_rule__max_avail +msgid "Maximum simultaneous availability on own Booking Engine" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__default_max_avail +msgid "" +"Maximum simultaneous availability on own Booking Engine given no " +"availability rules. Use `-1` for using maximum simultaneous availability." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_massive_changes_wizard__max_avail +msgid "Maximum simultaneous availability on own Booking Engine." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan_rule__max_stay +msgid "Maximum stay" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan_rule__max_stay_arrival +msgid "Maximum stay if checkin is today" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Member Team" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__message_has_error +#: model:ir.model.fields,field_description:pms.field_pms_folio__message_has_error +#: model:ir.model.fields,field_description:pms.field_pms_property__message_has_error +#: model:ir.model.fields,field_description:pms.field_pms_reservation__message_has_error +#: model:ir.model.fields,field_description:pms.field_pms_room_type__message_has_error +msgid "Message Delivery error" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__invoice_warn_msg +msgid "Message for Invoice" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__sale_warn_msg +msgid "Message for Sales Order" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__sale_line_warn_msg +msgid "Message for Sales Order Line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__picking_warn_msg +msgid "Message for Stock Picking" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__message_ids +#: model:ir.model.fields,field_description:pms.field_pms_folio__message_ids +#: model:ir.model.fields,field_description:pms.field_pms_property__message_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__message_ids +#: model:ir.model.fields,field_description:pms.field_pms_room_type__message_ids +msgid "Messages" +msgstr "" + +#. module: pms +#: model:pms.amenity,name:pms.pms_amenity_microwave_oven +msgid "Microwave oven" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__min_price +msgid "Min. Price" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__min_quantity +msgid "Min. Quantity" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__min_stay +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__min_stay +msgid "Min. Stay" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__min_stay_arrival +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__min_stay_arrival +msgid "Min. Stay Arrival" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_availability_plan_rule.py:0 +#, python-format +msgid "Min. Stay Arrival can't be less than zero" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_availability_plan_rule.py:0 +#, python-format +msgid "Min. Stay can't be less than zero" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__orderpoint_ids +msgid "Minimum Stock Rules" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__min_price +msgid "Minimum price for a room type" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan_rule__min_stay +msgid "Minimum stay" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan_rule__min_stay_arrival +msgid "Minimum stay if checkin is today" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__mobile +#: model:ir.model.fields,field_description:pms.field_pms_folio__mobile +#: model:ir.model.fields,field_description:pms.field_pms_property__mobile +#: model:ir.model.fields,field_description:pms.field_pms_reservation__mobile +#: model:ir.model.fields,field_description:pms.field_res_partner__mobile +#: model:ir.model.fields,field_description:pms.field_res_users__mobile +msgid "Mobile" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Mobile (Optional)" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_ir_pms_property__model_id +#: model_terms:ir.ui.view,arch_db:pms.advanced_filters_wizard +msgid "Model" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__property_modified_template +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Modification Email" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__modification_type +msgid "Modification Type" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__property_modified_template +msgid "Modification email template" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__cancelled_reason__modified +msgid "Modified" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.wizard_folio_changes_view_form +msgid "Monday" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_property__default_invoicing_policy__month_day +#: model:ir.model.fields.selection,name:pms.selection__res_partner__invoicing_policy__month_day +msgid "Month Day Invoice" +msgstr "" + +#. module: pms +#: code:addons/pms/models/product_product.py:0 +#, python-format +msgid "More than one room found for the same product" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.res_users_view_form +msgid "Multi Properties" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__my_activity_date_deadline +#: model:ir.model.fields,field_description:pms.field_pms_folio__my_activity_date_deadline +#: model:ir.model.fields,field_description:pms.field_pms_property__my_activity_date_deadline +#: model:ir.model.fields,field_description:pms.field_pms_reservation__my_activity_date_deadline +#: model:ir.model.fields,field_description:pms.field_pms_room_type__my_activity_date_deadline +msgid "My Activity Deadline" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "My Reservations" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_automated_mails__name +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__name +#: model:ir.model.fields,field_description:pms.field_pms_property__name +#: model:ir.model.fields,field_description:pms.field_pms_room_type__name +#: model:ir.model.fields,field_description:pms.field_pms_team_member__name +#: model:ir.model.fields,field_description:pms.field_room_closure_reason__name +#: model_terms:ir.ui.view,arch_db:pms.availability_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_form +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folio_invitations +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "Name" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_form +msgid "Name in reports" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan__name +msgid "Name of availability plan" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_automated_mails__name +msgid "Name of the automated mail." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type_class__name +msgid "Name of the room type class" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__partner_id +msgid "Name of who made the reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_account_move_line__name_changed_by_user +msgid "Name set manually" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Name *" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__nationality_id +#: model:ir.model.fields,field_description:pms.field_pms_property__nationality_id +#: model:ir.model.fields,field_description:pms.field_res_partner__nationality_id +#: model:ir.model.fields,field_description:pms.field_res_users__nationality_id +msgid "Nationality" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Nationality *" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__res_partner__invoice_to_agency__never +msgid "Never" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_checkin_partner.py:0 +#: code:addons/pms/models/pms_folio.py:0 code:addons/pms/models/pms_folio.py:0 +#: code:addons/pms/models/pms_folio.py:0 +#: code:addons/pms/models/pms_reservation.py:0 +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "New" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__new_board_service_id +msgid "New Board Service" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__new_checkin +msgid "New Checkin" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__new_checkout +msgid "New Checkout" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__new_discount +msgid "New Discount %" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__new_price +msgid "New Price" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__new_service_id +msgid "New Service" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "Next 7 days" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__activity_date_deadline +#: model:ir.model.fields,field_description:pms.field_pms_folio__activity_date_deadline +#: model:ir.model.fields,field_description:pms.field_pms_property__activity_date_deadline +#: model:ir.model.fields,field_description:pms.field_pms_reservation__activity_date_deadline +#: model:ir.model.fields,field_description:pms.field_pms_room_type__activity_date_deadline +msgid "Next Activity Deadline" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__activity_summary +#: model:ir.model.fields,field_description:pms.field_pms_folio__activity_summary +#: model:ir.model.fields,field_description:pms.field_pms_property__activity_summary +#: model:ir.model.fields,field_description:pms.field_pms_reservation__activity_summary +#: model:ir.model.fields,field_description:pms.field_pms_room_type__activity_summary +msgid "Next Activity Summary" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__activity_type_id +#: model:ir.model.fields,field_description:pms.field_pms_folio__activity_type_id +#: model:ir.model.fields,field_description:pms.field_pms_property__activity_type_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__activity_type_id +#: model:ir.model.fields,field_description:pms.field_pms_room_type__activity_type_id +msgid "Next Activity Type" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__reservation_line_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__nights +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__nights +msgid "Nights" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Nights)" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "No Checkins!" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__cancelled_reason__noshow +#: model_terms:ir.ui.view,arch_db:pms.pms_cancelation_rule_form +msgid "No Show" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__apply_on_noshow +msgid "No Show apply on" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "No checkin was made for this reservation" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "No entry has been recorded in this reservation" +msgstr "" + +#. module: pms +#: code:addons/pms/models/account_move.py:0 +#, python-format +msgid "" +"No journal could be found in company %(company_name)s\n" +" for any of those types: %(journal_types)s" +msgstr "" + +#. module: pms +#: code:addons/pms/models/account_move.py:0 +#, python-format +msgid "" +"No journal could be found in property %(property_name)s\n" +" for any of those types: %(journal_types)s" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "No person from reserve %s has arrived" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__days_noshow +msgid "NoShow first days" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_booking_duplicate__reservation_type__normal +#: model:ir.model.fields.selection,name:pms.selection__pms_booking_engine__reservation_type__normal +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__reservation_type__normal +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__reservation_type__normal +msgid "Normal" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__journal_normal_invoice_id +msgid "Normal Invoice Journal" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__email_normalized +msgid "Normalized Email" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__payment_state__not_paid +msgid "Not Paid" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_property.py:0 +#, python-format +msgid "Not invoiced due to pending amounts and cancelled reservations" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__folio_sale_line__display_type__line_note +msgid "Note" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__comment +#: model:ir.model.fields,field_description:pms.field_res_partner__comment +#: model:ir.model.fields,field_description:pms.field_res_users__comment +msgid "Notes" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__folio_sale_line__invoice_status__no +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__invoice_status__no +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__invoice_status__no +#: model:ir.model.fields.selection,name:pms.selection__pms_service__invoice_status__no +msgid "Nothing to Invoice" +msgstr "" + +#. module: pms +#: code:addons/pms/wizards/folio_make_invoice_advance.py:0 +#, python-format +msgid "Nothing to invoice" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__payment_state__nothing_to_pay +msgid "Nothing to pay" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__cardex_warning +msgid "Notice under the signature on the traveler's ticket." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__message_needaction_counter +#: model:ir.model.fields,field_description:pms.field_pms_folio__message_needaction_counter +#: model:ir.model.fields,field_description:pms.field_pms_property__message_needaction_counter +#: model:ir.model.fields,field_description:pms.field_pms_reservation__message_needaction_counter +#: model:ir.model.fields,field_description:pms.field_pms_room_type__message_needaction_counter +msgid "Number of Actions" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__number_of_cancelled_rooms +msgid "Number of Cancelled Rooms" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__folios_count +#: model:ir.model.fields,field_description:pms.field_res_partner__folios_count +#: model:ir.model.fields,field_description:pms.field_res_users__folios_count +msgid "Number of Folios" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__reservations_count +#: model:ir.model.fields,field_description:pms.field_res_partner__reservations_count +#: model:ir.model.fields,field_description:pms.field_res_users__reservations_count +msgid "Number of Reservations" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__number_of_rooms +msgid "Number of Rooms" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__value_num_rooms_selected +msgid "Number of Rooms Selected" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__number_of_services +msgid "Number of Services" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__number_of_cancelled_rooms +msgid "Number of cancelled rooms in folio." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__checkin_partner_count +msgid "Number of checkin partners in a reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__checkin_partner_pending_count +msgid "Number of checkin partners pending to checkin in a reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__children_occupying +msgid "Number of children there in guest list whose presence counts" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__message_has_error_counter +#: model:ir.model.fields,field_description:pms.field_pms_folio__message_has_error_counter +#: model:ir.model.fields,field_description:pms.field_pms_property__message_has_error_counter +#: model:ir.model.fields,field_description:pms.field_pms_reservation__message_has_error_counter +#: model:ir.model.fields,field_description:pms.field_pms_room_type__message_has_error_counter +msgid "Number of errors" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room__extra_beds_allowed +msgid "Number of extra beds allowed in room" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__folios_count +#: model:ir.model.fields,help:pms.field_res_partner__folios_count +#: model:ir.model.fields,help:pms.field_res_users__folios_count +msgid "Number of folios of the partner" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__count_pending_arrival +msgid "Number of guest with pending checkin" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__message_needaction_counter +#: model:ir.model.fields,help:pms.field_pms_folio__message_needaction_counter +#: model:ir.model.fields,help:pms.field_pms_property__message_needaction_counter +#: model:ir.model.fields,help:pms.field_pms_reservation__message_needaction_counter +#: model:ir.model.fields,help:pms.field_pms_room_type__message_needaction_counter +msgid "Number of messages which requires an action" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__message_has_error_counter +#: model:ir.model.fields,help:pms.field_pms_folio__message_has_error_counter +#: model:ir.model.fields,help:pms.field_pms_property__message_has_error_counter +#: model:ir.model.fields,help:pms.field_pms_reservation__message_has_error_counter +#: model:ir.model.fields,help:pms.field_pms_room_type__message_has_error_counter +msgid "Number of messages with delivery error" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__nights +msgid "Number of nights of a reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__pricelist_item_count +msgid "Number of price rules" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__reservations_count +#: model:ir.model.fields,help:pms.field_res_partner__reservations_count +#: model:ir.model.fields,help:pms.field_res_users__reservations_count +msgid "Number of reservations of the partner" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__availability +msgid "Number of rooms available" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__number_of_rooms +msgid "Number of rooms in folio. Canceled rooms do not count." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio_availability_wizard__num_rooms_available +msgid "Number of rooms that are available" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__number_of_services +msgid "Number of services in the folio" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__product_qty +msgid "Number of services that were sold" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__message_unread_counter +#: model:ir.model.fields,help:pms.field_pms_folio__message_unread_counter +#: model:ir.model.fields,help:pms.field_pms_property__message_unread_counter +#: model:ir.model.fields,help:pms.field_pms_reservation__message_unread_counter +#: model:ir.model.fields,help:pms.field_pms_room_type__message_unread_counter +msgid "Number of unread messages" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__children +msgid "" +"Number total of children there in guest list,whose presence counts or not" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__occupied_room +msgid "Occupied Room" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__occupies_availability +msgid "Occupies" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__massive_changes_on +msgid "On" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_checkin_partner__state__onboard +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__state__onboard +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "On Board" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +msgid "On Board Tomorrow" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_sale_channel__is_on_line +msgid "On Line" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Only Room" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Only Services" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__agency_id +msgid "Only allowed if the field of partner is_agency is True" +msgstr "" + +#. module: pms +#: model:ir.model.constraint,message:pms.constraint_pms_availability_room_type_registry_unique +msgid "" +"Only can exists one availability in the same day for" +" the same room type!" +msgstr "" + +#. module: pms +#: model:ir.model.constraint,message:pms.constraint_pms_availability_plan_rule_room_type_registry_unique +msgid "" +"Only can exists one availability rule in the same " +"day for the same room type!" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "Only can payment by property" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_board_service_room_type.py:0 +#, python-format +msgid "Only can set one default board service" +msgstr "" + +#. module: pms +#: code:addons/pms/wizards/wizard_several_partners.py:0 +#, python-format +msgid "Only one customer can be added to the reservation" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_bottom_tree +msgid "Open Reservation Room Detail" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_open_several_partners_wizard +msgid "Open Several Partners" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__operation +msgid "Operation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_split_join_swap_wizard__operation +msgid "Operation to be applied on the reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_team_member__pms_role__manager +msgid "Operational Manager" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_reservation_split_join_swap_wizard +msgid "Operations in reservations" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.report_folio_document +msgid "Order #" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__count +msgid "Order Count" +msgstr "" + +#. module: pms +#: code:addons/pms/controllers/pms_portal.py:0 +#: model:ir.model.fields,field_description:pms.field_pms_folio__date_order +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folio +#, python-format +msgid "Order Date" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__date_order +msgid "Order date of reservation" +msgstr "" + +#. module: pms +#: code:addons/pms/models/folio_sale_line.py:0 +#, python-format +msgid "Ordered Quantity: %(old_qty)s -> %(new_qty)s" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__invoice_policy +msgid "" +"Ordered Quantity: Invoice quantities ordered by the customer.\n" +"Delivered Quantity: Invoice quantities delivered to the customer." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_account_bank_statement_line__origin_agency_id +#: model:ir.model.fields,field_description:pms.field_account_move__origin_agency_id +#: model:ir.model.fields,field_description:pms.field_account_move_line__origin_agency_id +#: model:ir.model.fields,field_description:pms.field_account_payment__origin_agency_id +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__origin_agency_id +msgid "Origin Agency" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_account_payment__origin_reference +msgid "Origin Reference" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.view_account_invoice_filter +#: model_terms:ir.ui.view,arch_db:pms.view_account_move_filter +#: model_terms:ir.ui.view,arch_db:pms.view_account_payment_search +msgid "Originating agency" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_checkin_partner__gender__other +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Other" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Other data" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 code:addons/pms/models/pms_folio.py:0 +#: code:addons/pms/models/pms_folio.py:0 +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +#, python-format +msgid "Others" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_checkin_partner__state__done +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__state__done +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +msgid "Out" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_booking_duplicate__reservation_type__out +#: model:ir.model.fields.selection,name:pms.selection__pms_booking_engine__reservation_type__out +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__reservation_type__out +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__reservation_type__out +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Out of Service" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Out service description" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__outgoing_qty +msgid "Outgoing" +msgstr "" + +#. module: pms +#: code:addons/pms/models/account_move.py:0 +#, python-format +msgid "Outstanding credits" +msgstr "" + +#. module: pms +#: code:addons/pms/models/account_move.py:0 +#, python-format +msgid "Outstanding debits" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__overbooking +msgid "Overbooking" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Overbookings" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__payment_state__overpayment +msgid "Overpayment" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Overpayment!" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "" +"PART\n" +" OF TRAVELERS ENTRY" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "PART OF TRAVELERS ENTRY" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_folio +msgid "PMS Folio" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.view_partner_property_form +msgid "PMS Invoice policy" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_pms_move_out_invoice_type +msgid "PMS Invoices" +msgstr "" + +#. module: pms +#: model:ir.ui.menu,name:pms.pms_management_menu +msgid "PMS Management" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_team_member__pms_role +msgid "PMS Role" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.product_template_view_form +msgid "PMS Service" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_team_member +msgid "PMS Team Member" +msgstr "" + +#. module: pms +#: model:ir.actions.report,name:pms.action_report_pms_pro_forma_invoice +msgid "PRO-FORMA Invoice" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__payment_state__paid +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Paid" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__invoices_paid +msgid "Paid Out" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability__parent_avail_id +msgid "Parent Avail" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__parent_id +msgid "Parent Property" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room__parent_id +msgid "Parent Room" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability__parent_avail_id +msgid "Parent availability for this availability" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__parent_name +msgid "Parent name" +msgstr "" + +#. module: pms +#: model:pms.room.type,name:pms.pms_room_type_parking +#: model:pms.room.type.class,name:pms.pms_room_type_class_parking +#: model:product.product,name:pms.pms_room_type_parking_product_product +msgid "Parking" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Partial" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__payment_state__partial +msgid "Partially Paid" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__partner_id +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__partner_id +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__partner_id +#: model:ir.model.fields,field_description:pms.field_pms_folio__partner_id +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__partner_id +#: model_terms:ir.ui.view,arch_db:pms.booking_duplicate +#: model_terms:ir.ui.view,arch_db:pms.booking_engine +#: model_terms:ir.ui.view,arch_db:pms.view_partner_data_form +msgid "Partner" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_checkin_partner +msgid "Partner Checkins" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__contract_ids +msgid "Partner Contracts" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_res_partner_id_category +msgid "Partner ID Category" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_res_partner_id_number +msgid "Partner ID Number" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_several_partners_wizard +msgid "Partner Operations" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__partner_requests +msgid "Partner Requests" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_res_partner_category +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__segmentation_ids +#: model:ir.model.fields,help:pms.field_pms_booking_engine__segmentation_ids +msgid "Partner Tags" +msgstr "" + +#. module: pms +#: code:addons/pms/models/res_partner_id_number.py:0 +#, python-format +msgid "Partner already has this document type" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__partner_id +msgid "Partner associated with checkin partner" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Partner contact name is required" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__residence_country_id +#: model:ir.model.fields,help:pms.field_res_partner__residence_country_id +#: model:ir.model.fields,help:pms.field_res_users__residence_country_id +msgid "Partner country of residence" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__partner_name +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__partner_name +msgid "Partner name" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__partner_relationship +msgid "Partner relationship" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__residence_state_id +#: model:ir.model.fields,help:pms.field_res_partner__residence_state_id +#: model:ir.model.fields,help:pms.field_res_users__residence_state_id +msgid "Partner state of residence" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__partner_id +#: model:ir.model.fields,help:pms.field_pms_booking_engine__partner_id +msgid "Partner who made the reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__same_vat_partner_id +msgid "Partner with same Tax ID" +msgstr "" + +#. module: pms +#: code:addons/pms/models/res_partner.py:0 +#, python-format +msgid "Partner without document identification, update vals %s" +msgstr "" + +#. module: pms +#: model:res.partner.id_category,name:pms.document_type_passport +msgid "Passport" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_folio_payment +#: model_terms:ir.ui.view,arch_db:pms.wizard_payment_folio_view_form +msgid "Pay" +msgstr "" + +#. module: pms +#: code:addons/pms/controllers/pms_portal.py:0 +#, python-format +msgid "Pay & Confirm" +msgstr "" + +#. module: pms +#: code:addons/pms/models/payment_transaction.py:0 +#: model_terms:ir.ui.view,arch_db:pms.portal_folio_payment +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_end +#, python-format +msgid "Pay Now" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_folio_payment +msgid "Pay with" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__debit_limit +msgid "Payable Limit" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.wizard_payment_folio_view_form +msgid "Payment" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_payment_acquirer +msgid "Payment Acquirer" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_payment_folio +msgid "Payment Folio" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__property_payment_method_id +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__payment_method_id +msgid "Payment Method" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Payment Mode" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +msgid "Payment Pending" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__reference +msgid "Payment Ref." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__folio_payment_state +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Payment State" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__payment_state +msgid "Payment Status" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__payment_term_id +msgid "Payment Terms" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__payment_token_ids +msgid "Payment Tokens" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_payment_transaction +msgid "Payment Transaction" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__payment_term_id +msgid "Payment terms for current folio." +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "Payment: %s by %s" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_account_payment +#: model:ir.model,name:pms.model_wizard_payment_folio +#: model:ir.model.fields,help:pms.field_pms_folio__payment_ids +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Payments" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__transaction_ids +msgid "Payments made through payment acquirer" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__pending_amount +#: model:ir.model.fields,field_description:pms.field_pms_reservation__folio_pending_amount +msgid "Pending Amount" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__count_rooms_pending_arrival +#: model:ir.model.fields,field_description:pms.field_pms_reservation__count_pending_arrival +msgid "Pending Arrival" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__checkins_ratio +msgid "Pending Arrival Ratio" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__ratio_checkin_data +msgid "Pending Checkin Data" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_checkin_partner__state__precheckin +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__state__confirm +msgid "Pending arrival" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_service__per_day +msgid "Per Day" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__commission_percent +msgid "Percentage corresponding to commission" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_cancelation_rule__penalty_late +msgid "" +"Percentage of the total price that partner has to pay in case of late " +"arrival" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_cancelation_rule__penalty_noshow +msgid "" +"Percentage of the total price that partner has to pay in case of no show" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_checkin_partner.py:0 +#, python-format +msgid "Personal data is missing for check-in" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Persons" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_room.py:0 +#, python-format +msgid "Persons can't be higher than room capacity (%s)" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__phone +#: model:ir.model.fields,field_description:pms.field_pms_property__phone +#: model:ir.model.fields,field_description:pms.field_res_partner__phone +#: model:ir.model.fields,field_description:pms.field_res_users__phone +msgid "Phone" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Phone (Optional)" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__plan_avail +msgid "Plan Avail" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "Please define an accounting sales journal for the company %s (%s)." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_account_analytic_distribution__pms_property_id +#: model:ir.model.fields,field_description:pms.field_account_analytic_line__pms_property_id +#: model:ir.model.fields,field_description:pms.field_account_move_line__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__pms_property_id +msgid "Pms Property" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__pos_categ_id +msgid "Point of Sale Category" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__access_url +#: model:ir.model.fields,field_description:pms.field_pms_folio__access_url +#: model:ir.model.fields,field_description:pms.field_pms_reservation__access_url +msgid "Portal Access URL" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__pos_order_ids +msgid "Pos Order" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__pos_order_count +msgid "Pos Order Count" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__checkin_partner_possible_customer_id +#: model:ir.model.fields,field_description:pms.field_res_partner__checkin_partner_possible_customer_id +#: model:ir.model.fields,field_description:pms.field_res_users__checkin_partner_possible_customer_id +msgid "Possible Customer In Checkin Partner" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__folio_possible_customer_id +#: model:ir.model.fields,field_description:pms.field_res_partner__folio_possible_customer_id +#: model:ir.model.fields,field_description:pms.field_res_users__folio_possible_customer_id +msgid "Possible Customer In Folio" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__reservation_possible_customer_id +#: model:ir.model.fields,field_description:pms.field_res_partner__reservation_possible_customer_id +#: model:ir.model.fields,field_description:pms.field_res_users__reservation_possible_customer_id +msgid "Possible Customer In Reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__possible_existing_customer_ids +#: model:ir.model.fields,field_description:pms.field_pms_folio__possible_existing_customer_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__possible_existing_customer_ids +msgid "Possible existing customer" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.several_partners_wizard +msgid "Possibles customers" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__state__draft +msgid "Pre-reservation" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation_precheckin +msgid "PreCheckin in" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__property_payment_method_id +msgid "" +"Preferred payment method when paying this vendor. This is used to filter " +"vendor bills by preferred payment method to register payments in mass. Use " +"cases: create bank files for batch wires, check runs." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__prepaid_warning_days +msgid "Prepaid Warning Days" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__price +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__price +#: model:ir.model.fields,field_description:pms.field_pms_room_type__price +msgid "Price" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__price_reduce +msgid "Price Reduce" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__price_reduce_taxexcl +msgid "Price Reduce Tax excl" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__price_reduce_taxinc +msgid "Price Reduce Tax inc" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation_detail +msgid "Price Total" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Price and Availability Plans" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__list_price +#: model:ir.model.fields,help:pms.field_product_product__list_price +#: model:ir.model.fields,help:pms.field_product_template__list_price +msgid "Price at which the product is sold to customers." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service_line__amount +msgid "Price for this Board Service Line/Product" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service_room_type_line__amount +msgid "Price for this Board Service Room Type Line/Product" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service__amount +#: model:ir.model.fields,help:pms.field_pms_board_service_room_type__amount +msgid "" +"Price for this Board Service. It corresponds to the sum of his board service" +" lines" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__price_per_room +msgid "Price per room" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio_availability_wizard__price_per_room +msgid "Price per room in folio" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service_line__price_unit +msgid "Price per unit of service" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__price_reduce_taxinc +msgid "Price with discounts applied and taxes included" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__price_reduce_taxexcl +msgid "Price with discounts applied without taxes" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__amount_tax +msgid "Price with taxes on a folio" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_product_pricelist +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__pricelist_id +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__pricelist_id +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__pricelist_ids +#: model:ir.model.fields,field_description:pms.field_pms_folio__pricelist_id +#: model:ir.model.fields,field_description:pms.field_pms_property__property_product_pricelist +#: model:ir.model.fields,field_description:pms.field_pms_reservation__pricelist_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__pricelist_id +#: model:ir.model.fields,field_description:pms.field_pms_room_type__pricelist_id +#: model:ir.model.fields,field_description:pms.field_product_pricelist_item__pricelist_id +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__new_pricelist_id +#: model:ir.model.fields.selection,name:pms.selection__pms_massive_changes_wizard__massive_changes_on__pricelist +#: model_terms:ir.ui.view,arch_db:pms.booking_duplicate +#: model_terms:ir.ui.view,arch_db:pms.booking_engine +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Pricelist" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__pricelist_items_to_overwrite +msgid "Pricelist Items to Override" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__pricelist_readonly +msgid "Pricelist Readonly" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_product_pricelist_item +msgid "Pricelist Rule" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_product_pricelist__pricelist_type +msgid "Pricelist Type" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__pricelist_id +#: model:ir.model.fields,help:pms.field_pms_booking_engine__pricelist_id +msgid "Pricelist applied in folio" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__pricelist_id +msgid "Pricelist for current folio." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_product_pricelist_item__pricelist_id +msgid "Pricelist in which this item is included" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__num_pricelist_items_to_overwrite +msgid "Pricelist items to overwrite on massive changes" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__pricelist_id +msgid "Pricelist that guides the prices of the reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_cancelation_rule__pricelist_ids +msgid "Pricelist that use this rule" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__pricelist_ids +msgid "Pricelist to apply massive changes" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_product_pricelist__pricelist_type +msgid "Pricelist types, it can be Daily Plan" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_duplicate__pricelist_id +msgid "Pricelist used for this reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan__pms_pricelist_ids +#: model:ir.model.fields,field_description:pms.field_pms_sale_channel__product_pricelist_ids +#: model:ir.ui.menu,name:pms.pricelist_menu +msgid "Pricelists" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.product_pricelist_item_action2 +msgid "Pricelists Items" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_sale_channel__product_pricelist_ids +msgid "Pricelists for a sale channel" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan__pms_pricelist_ids +msgid "Pricelists of the availability plan " +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "" +"Prices have been recomputed according to pricelist %s\n" +" and room type %s" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.folio_portal_template +msgid "Print" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Print All Checkins" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_form +msgid "Print in PDF" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Print in cardex" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__priority +#: model:ir.model.fields,field_description:pms.field_res_country__priority +#: model:ir.model.fields,field_description:pms.field_res_partner_id_category__priority +msgid "Priority" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__priority +msgid "Priority of a reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__privacy_policy +#: model:ir.model.fields,field_description:pms.field_res_company__privacy_policy +#: model_terms:ir.ui.view,arch_db:pms.company_view_form +msgid "Privacy Policy" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_amenity_view_form +msgid "Procurement" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_product_product +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__product_id +#: model:ir.model.fields,field_description:pms.field_pms_board_service_line__product_id +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type_line__product_id +#: model:ir.model.fields,field_description:pms.field_pms_room_type__product_variant_id +#: model:ir.model.fields,field_description:pms.field_pms_service_line__product_id +#: model:ir.model.fields,field_description:pms.field_product_pricelist_item__product_id +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "Product" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__attribute_line_ids +msgid "Product Attributes" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__categ_id +msgid "Product Category" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_service__product_image +msgid "Product Image" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__packaging_ids +msgid "Product Packages" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__default_pricelist_id +msgid "Product Pricelist" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__product_id +msgid "Product Room Type" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_product_template +#: model:ir.model.fields,field_description:pms.field_pms_room_type__product_tmpl_id +#: model:ir.model.fields,field_description:pms.field_product_pricelist_item__product_tmpl_id +msgid "Product Template" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__type +msgid "Product Type" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__product_uom_readonly +msgid "Product Uom Readonly" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__product_id +msgid "" +"Product associated with folio sale line, can be product associated with " +"service or product associated withreservation's room type, in other case " +"it's false" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_product_pricelist_item__product_id +msgid "Product associated with the item" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service_line__product_id +msgid "Product associated with this board service line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service_room_type_line__product_id +msgid "Product associated with this board service room type line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__product_id +msgid "Product associated with this service" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service_line__product_id +msgid "Product associated with this service line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__product_id +msgid "Product identifier associated with room type" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_product_pricelist_item__product_tmpl_id +msgid "Product template associated with the item" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_res_company__cancel_penalty_product_id +msgid "Product used to calculate the cancel penalty" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__property_stock_production +msgid "Production Location" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__product_variant_ids +msgid "Products" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.pms_property_action +#: model:ir.model.fields,field_description:pms.field_account_journal__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_ir_pms_property__pms_property_id +#: model:ir.model.fields,field_description:pms.field_payment_acquirer__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_pms_amenity__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_pms_amenity_type__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_pms_board_service__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_pms_board_service_line__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_pms_cancelation_rule__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_pms_property__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_pms_room_type__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_pms_sale_channel__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_pms_ubication__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_product_pricelist__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_product_pricelist_item__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_product_product__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_product_template__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_res_company__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_res_partner__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_res_users__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_room_closure_reason__pms_property_ids +#: model:ir.ui.menu,name:pms.general_property_menu +#: model:ir.ui.menu,name:pms.pms_property_menu +msgid "Properties" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_bank_statement__pms_property_id +#: model:ir.model.fields,help:pms.field_pms_availability_plan_rule__pms_property_id +#: model:ir.model.fields,help:pms.field_res_company__pms_property_ids +msgid "Properties with access to the element" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__pms_property_ids +#: model:ir.model.fields,help:pms.field_res_partner__pms_property_ids +msgid "" +"Properties with access to the element if not set, all properties can access" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_journal__pms_property_ids +#: model:ir.model.fields,help:pms.field_payment_acquirer__pms_property_ids +#: model:ir.model.fields,help:pms.field_pms_amenity__pms_property_ids +#: model:ir.model.fields,help:pms.field_pms_amenity_type__pms_property_ids +#: model:ir.model.fields,help:pms.field_pms_availability_plan__pms_property_ids +#: model:ir.model.fields,help:pms.field_pms_board_service__pms_property_ids +#: model:ir.model.fields,help:pms.field_pms_board_service_line__pms_property_ids +#: model:ir.model.fields,help:pms.field_pms_cancelation_rule__pms_property_ids +#: model:ir.model.fields,help:pms.field_pms_room__pms_property_id +#: model:ir.model.fields,help:pms.field_pms_room_type__pms_property_ids +#: model:ir.model.fields,help:pms.field_pms_room_type_class__pms_property_ids +#: model:ir.model.fields,help:pms.field_pms_sale_channel__pms_property_ids +#: model:ir.model.fields,help:pms.field_pms_ubication__pms_property_ids +#: model:ir.model.fields,help:pms.field_product_pricelist__pms_property_ids +#: model:ir.model.fields,help:pms.field_product_pricelist_item__pms_property_ids +#: model:ir.model.fields,help:pms.field_product_product__pms_property_ids +#: model:ir.model.fields,help:pms.field_product_template__pms_property_ids +#: model:ir.model.fields,help:pms.field_room_closure_reason__pms_property_ids +msgid "" +"Properties with access to the element; if not set, all properties can access" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio_availability_wizard__pms_property_id +#: model:ir.model.fields,help:pms.field_pms_reservation_duplicate__pms_property_id +msgid "Propertiy with access to the element;" +msgstr "" + +#. module: pms +#. openerp-web +#: code:addons/pms/static/src/js/reconciliation_widget.js:0 +#: code:addons/pms/static/src/xml/account_reconciliation.xml:0 +#: model:ir.model,name:pms.model_pms_property +#: model:ir.model.fields,field_description:pms.field_account_bank_statement__pms_property_id +#: model:ir.model.fields,field_description:pms.field_account_bank_statement_line__pms_property_id +#: model:ir.model.fields,field_description:pms.field_account_move__pms_property_id +#: model:ir.model.fields,field_description:pms.field_account_payment__pms_property_id +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_availability__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type_line__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_folio__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__pms_property_ids +#: model:ir.model.fields,field_description:pms.field_pms_property__partner_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_room__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_service__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_service_line__pms_property_id +#: model:ir.model.fields,field_description:pms.field_pms_team_member__pms_property_id +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_room_type_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +#: model_terms:ir.ui.view,arch_db:pms.view_account_invoice_filter +#: model_terms:ir.ui.view,arch_db:pms.view_account_journal_search +#: model_terms:ir.ui.view,arch_db:pms.view_account_move_filter +#: model_terms:ir.ui.view,arch_db:pms.view_account_move_line_filter +#: model_terms:ir.ui.view,arch_db:pms.view_account_payment_search +#, python-format +msgid "Property" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__pms_property_code +msgid "Property Code" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Property Configuration" +msgstr "" + +#. module: pms +#: model:res.groups,name:pms.group_pms_call +msgid "Property Management / CallCenter" +msgstr "" + +#. module: pms +#: model:res.groups,name:pms.group_pms_user +msgid "Property Management / User" +msgstr "" + +#. module: pms +#: model:res.groups,name:pms.group_pms_manager +msgid "Property Management/ Manager" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__res_partner__invoicing_policy__property +msgid "Property Policy Invoice" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_room +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_form +msgid "Property Room" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room__room_type_id +#: model_terms:ir.ui.view,arch_db:pms.pms_room_type_view_form +msgid "Property Room Type" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__class_id +msgid "Property Type Class" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_ubication_view_form +msgid "Property Ubication" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_ubication.py:0 +#, python-format +msgid "Property not allowed" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_availability.py:0 +#, python-format +msgid "Property not allowed on availability day compute" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability__pms_property_id +msgid "Property to which the availability is directed" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__pms_property_id +msgid "Property to which the folio associated belongs" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__pms_property_id +#: model:ir.model.fields,help:pms.field_pms_booking_engine__pms_property_id +msgid "Property to which the folio belongs" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__pms_property_id +msgid "Property to which the reservation belongs" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__pms_property_id +#: model:ir.model.fields,help:pms.field_pms_service_line__pms_property_id +msgid "Property to which the service belongs" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_line__pms_property_id +msgid "Property with access to the element" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__pms_property_id +msgid "Property with access to the element;" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service_room_type_line__pms_property_id +msgid "" +"Property with access to the element; if not set, all properties can access" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service_room_type__pms_property_id +msgid "" +"Property with access to the element; if not set, all property can access" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__ratio_checkin_data +msgid "Proportion of guest data complete at checkin" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__checkins_ratio +msgid "Proportion of guest pending checkin" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__lst_price +msgid "Public Price" +msgstr "" + +#. module: pms +#: model:product.pricelist,name:pms.pricelist_discount_10_percent +msgid "Public Pricelist Discount 10%" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__description_purchase +msgid "Purchase Description" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__uom_po_id +msgid "Purchase Unit of Measure" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__putaway_rule_ids +msgid "Putaway Rules" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_service_line__auto_qty +msgid "Qty automated setted" +msgstr "" + +#. module: pms +#: model:pms.room.type,name:pms.pms_room_type_quadruple +#: model:product.product,name:pms.pms_room_type_quadruple_product_product +msgid "Quadruple" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__product_uom_qty +#: model:ir.model.fields,field_description:pms.field_pms_service__product_qty +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation_detail +#: model_terms:ir.ui.view,arch_db:pms.report_folio_document +msgid "Quantity" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__qty_available +msgid "Quantity On Hand" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__quantity_svl +msgid "Quantity Svl" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__incoming_qty +msgid "" +"Quantity of planned incoming products.\n" +"In a context with a single Stock Location, this includes goods arriving to this Location, or any of its children.\n" +"In a context with a single Warehouse, this includes goods arriving to the Stock Location of this Warehouse, or any of its children.\n" +"Otherwise, this includes goods arriving to any Stock Location with 'internal' type." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__outgoing_qty +msgid "" +"Quantity of planned outgoing products.\n" +"In a context with a single Stock Location, this includes goods leaving this Location, or any of its children.\n" +"In a context with a single Warehouse, this includes goods leaving the Stock Location of this Warehouse, or any of its children.\n" +"Otherwise, this includes goods leaving any Stock Location with 'internal' type." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__day_qty +msgid "Quantity per day" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__visible_qty_configurator +msgid "Quantity visible in configurator" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__quota +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__quota +msgid "Quota" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__default_quota +msgid "" +"Quota assigned to the own Booking Engine given no availability rules. Use " +"`-1` for managing no quota." +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__state__draft +msgid "Quotation" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.report_folio_document +msgid "Quotation #" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__state__sent +msgid "Quotation Sent" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__expense_policy +msgid "Re-Invoice Expenses" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__visible_expense_policy +msgid "Re-Invoice Policy visible" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__ready_for_checkin +msgid "Ready for checkin" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability__real_avail +msgid "Real Avail" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__real_avail +msgid "Real availability" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.booking_duplicate +#: model_terms:ir.ui.view,arch_db:pms.booking_engine +msgid "Reason" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__cancelled_reason +msgid "Reason of cancellation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__closure_reason_id +msgid "Reason why the reservation cannot be made" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_team_member__pms_role__reception +msgid "Reception" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__user_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__user_id +msgid "Reception Manager" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_advanced_filters_wizard__pms_model_id +msgid "Recipients Model" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_advanced_filters_wizard__pms_model_name +msgid "Recipients Model Name" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__recompute_prices +msgid "Recompute Price" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Recompute all prices based on this pricelist" +msgstr "" + +#. module: pms +#: model:ir.actions.server,name:pms.priority_reservations_ir_actions_server +#: model:ir.cron,cron_name:pms.priority_reservations +#: model:ir.cron,name:pms.priority_reservations +msgid "Recompute priority on reservations" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_ir_pms_property__record +msgid "Record Id" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__price_reduce +msgid "Reduced price amount, that is, total price with discounts applied" +msgstr "" + +#. module: pms +#: code:addons/pms/controllers/pms_portal.py:0 +#: model:ir.model.fields,field_description:pms.field_pms_property__ref +#: model:ir.model.fields,field_description:pms.field_pms_room_type__code +#, python-format +msgid "Reference" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_ir_pms_property__value_reference +msgid "Reference Field Value" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__external_reference +#: model:ir.model.fields,help:pms.field_pms_reservation__external_reference +msgid "Reference of this folio in an external system" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "Refund: %s by %s" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Register Checkins" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Register Partners" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "Register Payment" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__folio_advance_payment_inv__advance_payment_method__delivered +msgid "Regular invoice" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Remove some of the leftover assigned checkins first" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__reordering_max_qty +msgid "Reordering Max Qty" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__reordering_min_qty +msgid "Reordering Min Qty" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__nbr_reordering_rules +msgid "Reordering Rules" +msgstr "" + +#. module: pms +#: model:ir.actions.report,name:pms.action_report_folio +msgid "Report Folio" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__is_reselling +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__is_reselling +msgid "Reselling" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_resend_cancellation_email +msgid "Resend cancellation email" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_resend_confirmation_email +msgid "Resend confirmation email" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_resend_modification_email +msgid "Resend modification email" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.open_pms_reservation_form_tree_all +#: model:ir.model,name:pms.model_pms_reservation +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__reservation_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__reservation_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__reservation_id +#: model:ir.model.fields,field_description:pms.field_pms_service_line__reservation_id +#: model:ir.model.fields,field_description:pms.field_pms_several_partners_wizard__reservation_id +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +#: model_terms:ir.ui.view,arch_db:pms.reservation_wizard +msgid "Reservation" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation +msgid "Reservation #" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.availability_action +msgid "Reservation Availability Plans" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__name +msgid "Reservation Code" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__name +msgid "Reservation Code Identification" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Reservation Detail" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Reservation Details" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__reservation_order +msgid "Reservation Id" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_end +msgid "Reservation Info" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_availability__reservation_line_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__reservation_line_ids +msgid "Reservation Lines" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Reservation Notes" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__reservation_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__reference_reservation_id +msgid "Reservation Reference" +msgstr "" + +#. module: pms +#: model:ir.ui.menu,name:pms.menu_reservation_rooms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Reservation Rooms" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_service_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_service_view_search +msgid "Reservation Service" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Reservation Total" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__reservation_type +msgid "Reservation Type" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_wizard_reservation_lines_split__reservation_wizard_id +msgid "Reservation Wizard" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_availability_plan +msgid "Reservation availability plan" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "Reservation dates should be consecutives" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#: code:addons/pms/models/pms_reservation.py:0 +#: code:addons/pms/wizards/pms_booking_duplicate.py:0 +#: code:addons/pms/wizards/pms_booking_engine.py:0 +#, python-format +msgid "Reservation from " +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__reservation_id +msgid "Reservation in which the service is included" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__reservation_line_ids +msgid "" +"Reservation lines associated with folio sale line, they corresponds with " +"nights" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_view_reservation_operations +msgid "Reservation operations" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_availability_plan_rule +msgid "Reservation rule by day" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_duplicate__reference_reservation_id +msgid "Reservation to copy data" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__reservation_id +msgid "Reservation to which checkin partners belong" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__reservation_id +msgid "Reservation to which folio sale line belongs" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.pms_partner_reservations +#: model:ir.model.fields,field_description:pms.field_account_bank_statement_line__reservation_ids +#: model:ir.model.fields,field_description:pms.field_pms_folio__reservation_ids +#: model:ir.model.fields,field_description:pms.field_pms_property__pms_reservation_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__reservation_ids +#: model:ir.model.fields,field_description:pms.field_res_partner__pms_reservation_ids +#: model:ir.model.fields,field_description:pms.field_res_users__pms_reservation_ids +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__reservation_ids +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__reservation_ids +#: model:ir.model.fields.selection,name:pms.selection__wizard_folio_changes__modification_type__reservations +#: model:ir.ui.menu,name:pms.menu_reservations +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_graph +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_pivot +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_calendar +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_timeline +#: model_terms:ir.ui.view,arch_db:pms.portal_my_home_menu_reservation +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservations +#: model_terms:ir.ui.view,arch_db:pms.res_partner_view_form +msgid "Reservations" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__reservation_lines_to_change +msgid "Reservations Lines To Change" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_reservation_line +msgid "Reservations by day" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_bank_statement_line__reservation_ids +msgid "Reservations in which the Account Bank Statement Lines are included" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.res_partner_view_form +msgid "Reservations related with this contact" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Reservations to 1 month" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Reservations to 14 days" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Reservations to 7 days" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_home_menu_reservation +msgid "Reservations/" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_form +#: model_terms:ir.ui.view,arch_db:pms.res_partner_view_form +msgid "Residence Address" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Residence Address *" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__responsible_id +msgid "Responsible" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__activity_user_id +#: model:ir.model.fields,field_description:pms.field_pms_folio__activity_user_id +#: model:ir.model.fields,field_description:pms.field_pms_property__activity_user_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__activity_user_id +#: model:ir.model.fields,field_description:pms.field_pms_room_type__activity_user_id +msgid "Responsible User" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_team_member__pms_role__revenue +msgid "Revenue" +msgstr "" + +#. module: pms +#: model:ir.ui.menu,name:pms.revenue_management_menu +msgid "Revenue Management" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__revenue_user_id +msgid "Revenue Manager" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__preferred_room_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__preferred_room_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__room_id +#: model:ir.model.fields,field_description:pms.field_pms_service__reservation_id +#: model:ir.model.fields,field_description:pms.field_pms_wizard_reservation_lines_split__room_id +#: model:pms.room.type.class,name:pms.pms_room_type_class_bedroom +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_search +#: model_terms:ir.ui.view,arch_db:pms.reservation_wizard +msgid "Room" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_pms_room_amenity_view_form +#: model:ir.model.fields,field_description:pms.field_pms_room__room_amenity_ids +#: model_terms:ir.ui.view,arch_db:pms.pms_amenity_search +#: model_terms:ir.ui.view,arch_db:pms.pms_amenity_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_room_type_view_form +msgid "Room Amenities" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_room_amenity_type_view_form +msgid "Room Amenities Type" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_room_type_class_view_form +msgid "Room Class" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.open_pms_room_closure_reason_form_tree +#: model_terms:ir.ui.view,arch_db:pms.pms_room_closure_reason_view_form +msgid "Room Closure Reason" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Room Line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room__name +#: model:ir.model.fields,help:pms.field_pms_room__name +msgid "Room Name" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_kanban_view +msgid "Room Reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__price_room_services_set +msgid "Room Services Total" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__room_source +msgid "Room Source" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation_split_join_swap_wizard__room_target +msgid "Room Target" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.open_pms_room_type_form_tree +#: model:ir.model,name:pms.model_pms_room_type +#: model:ir.model.fields,field_description:pms.field_pms_availability__room_type_id +#: model:ir.model.fields,field_description:pms.field_pms_availability_plan_rule__room_type_id +#: model:ir.model.fields,field_description:pms.field_pms_board_service_room_type__pms_room_type_id +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__room_type_id +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__room_type_ids +#: model:ir.model.fields,field_description:pms.field_pms_num_rooms_selection__room_type_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__room_type_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__room_type_id +#: model:ir.model.fields,field_description:pms.field_pms_room_type__room_type_id +#: model:ir.model.fields,field_description:pms.field_product_product__room_type_id +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_room_type_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_search +msgid "Room Type" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__room_amenity_ids +msgid "Room Type Amenities" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.open_pms_room_type_class_form_tree +#: model:ir.model,name:pms.model_pms_room_type_class +#: model:ir.ui.menu,name:pms.menu_open_pms_room_type_class_form_tree +msgid "Room Type Class" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service_room_type__pms_room_type_id +msgid "Room Type for which this Board Service is available" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio_availability_wizard__room_type_id +#: model:ir.model.fields,help:pms.field_pms_reservation_duplicate__room_type_id +msgid "Room Type reserved" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__room_type_id +msgid "" +"Room Type sold on the reservation,it doesn't necessarily correspond to the " +"room actually assigned" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_kanban +msgid "Room Type:" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_massive_changes_wizard__apply_pricelists_on__room_types +#: model:ir.ui.menu,name:pms.menu_open_pms_room_type_form_tree +msgid "Room Types" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type_class__room_type_ids +msgid "Room Types that belong to this Room Type Class" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_amenity +msgid "Room amenity" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_pms_room_amenity_type_view_form +msgid "Room amenity Type" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "" +"Room line Check In Date Should be less than the Check " +"Out Date!" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__reservation_ids +msgid "Room reservation detail" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_duplicate__preferred_room_id +msgid "Room reserved" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service_line__reservation_id +msgid "Room to which the services will be applied" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_availability +msgid "Room type availability per day" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type_class__default_code +msgid "Room type class identification code" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability__room_type_id +msgid "Room type for which availability is indicated" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan_rule__room_type_id +msgid "Room type for which availability rule is applied" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_folio_availability_wizard +msgid "Room type line in Booking Engine" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__board_service_room_type_ids +msgid "Room type's board services" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__room_type_filter_ids +msgid "Room types" +msgstr "" + +#. module: pms +#: code:addons/pms/wizards/wizard_split_join_swap_reservation.py:0 +#, python-format +msgid "Room {} not available." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__rooms +msgid "Room/s" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Room:" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Rooming" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__line_ids +#: model:ir.model.fields,field_description:pms.field_pms_property__room_ids +#: model:ir.model.fields,field_description:pms.field_pms_room_type__room_ids +#: model:ir.model.fields,field_description:pms.field_pms_ubication__pms_room_ids +#: model:ir.ui.menu,name:pms.menu_open_pms_room_form +#: model:ir.ui.menu,name:pms.pms_rooms_menu +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_bottom_tree +msgid "Rooms" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__free_room_ids +msgid "Rooms available" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_ubication__pms_room_ids +msgid "Rooms found in this location" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_reservation_duplicate +msgid "Rooms in Duplicate Folio" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_availability.py:0 +#, python-format +msgid "Rooms shared between different properties" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__room_ids +msgid "Rooms that a property has." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__rooms +msgid "Rooms that are reserved" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__room_ids +msgid "Rooms that belong to room type." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__line_ids +msgid "Rooms to create" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__route_ids +msgid "Routes" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__has_available_route_ids +msgid "Routes can be selected on this product" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__rules_to_overwrite +msgid "Rule to Overwrite" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.availability_view_form +msgid "Rules" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan__rule_ids +msgid "Rules in a availability plan" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__num_rules_to_overwrite +msgid "Rules to overwrite on massive changes" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__sii_exempt_cause +msgid "SII Exempt Cause" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.open_pms_sale_channel_form_tree +#: model:ir.model.fields,field_description:pms.field_pms_property__sale_channel_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__sale_channel_id +#: model:ir.model.fields,field_description:pms.field_res_partner__sale_channel_id +#: model:ir.model.fields,field_description:pms.field_res_users__sale_channel_id +#: model_terms:ir.ui.view,arch_db:pms.pms_sale_channel_view_form +msgid "Sale Channel" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_sale_channel__name +msgid "Sale Channel Name" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__sale_channel_origin_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__sale_channel_origin_id +#: model:ir.model.fields,field_description:pms.field_pms_service__sale_channel_origin_id +msgid "Sale Channel Origin" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_sale_channel__channel_type +msgid "Sale Channel Type" +msgstr "" + +#. module: pms +#: code:addons/pms/models/res_partner.py:0 +#, python-format +msgid "Sale Channel for an agency must be indirect" +msgstr "" + +#. module: pms +#: code:addons/pms/models/res_partner.py:0 +#, python-format +msgid "Sale Channel must be entered" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__sale_channel_origin_id +msgid "Sale Channel through which folio was created, the original" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_line__sale_channel_id +msgid "Sale Channel through which reservation line was created" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__sale_channel_origin_id +msgid "Sale Channel through which reservation was created, the original" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__sale_channel_origin_id +msgid "Sale Channel through which service was created, the original" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__sale_channel_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__sale_channel_ids +#: model:ir.ui.menu,name:pms.menu_open_pms_sale_channel_form_tree +msgid "Sale Channels" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__sale_channel_ids +msgid "Sale Channels through which reservation lines were managed" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__sale_channel_ids +msgid "Sale Channels through which reservations were managed" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.product_pricelist_item_view_tree +msgid "Sale Date End" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.product_pricelist_item_view_tree +msgid "Sale Date Start" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_massive_changes_wizard__date_types__sale_dates +msgid "Sale Dates" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room__description_sale +msgid "Sale Description" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Sale Details" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__sale_line_ids +#: model:ir.model.fields,field_description:pms.field_pms_service__sale_line_ids +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Sale Lines" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__sale_order_count +msgid "Sale Order Count" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_product_pricelist__pms_sale_channel_ids +msgid "Sale channel for which the pricelist is included" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__sale_line_ids +msgid "Sale lines" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__sale_line_ids +msgid "Sale lines in folio. It correspond with reservation nights" +msgstr "" + +#. module: pms +#: model:ir.ui.menu,name:pms.pms_sales_menu +msgid "Sales" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_sale_channel +msgid "Sales Channel" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__channel_type_id +#: model:ir.model.fields,help:pms.field_pms_booking_engine__channel_type_id +msgid "Sales Channel through which the reservation was managed" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__description_sale +msgid "Sales Description" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__sale_line_ids +#: model:ir.model.fields,field_description:pms.field_pms_service_line__sale_line_ids +msgid "Sales Lines" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__sale_order_ids +msgid "Sales Order" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__sale_line_warn +msgid "Sales Order Line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__list_price +#: model:ir.model.fields,field_description:pms.field_product_product__list_price +#: model:ir.model.fields,field_description:pms.field_product_template__list_price +msgid "Sales Price" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__team_id +msgid "Sales Team" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__sale_warn +msgid "Sales Warnings" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service_line__date +msgid "Sate on which the product is to be consumed" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.wizard_folio_changes_view_form +msgid "Saturday" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Save" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.advanced_filters_wizard +msgid "Search" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__lastname2 +msgid "Second Last Name" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Second Lastname (Optional)" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Second Street (Optional)" +msgstr "" + +#. module: pms +#: model:pms.ubication,name:pms.pms_ubication_2nd_floor +msgid "Second floor" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__lastname2 +#: model:ir.model.fields,field_description:pms.field_res_partner__lastname2 +#: model:ir.model.fields,field_description:pms.field_res_users__lastname2 +msgid "Second last name" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__residence_street2 +#: model:ir.model.fields,field_description:pms.field_res_partner__residence_street2 +#: model:ir.model.fields,field_description:pms.field_res_users__residence_street2 +msgid "Second street of residence" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__residence_street2 +#: model:ir.model.fields,help:pms.field_pms_property__residence_street2 +#: model:ir.model.fields,help:pms.field_res_partner__residence_street2 +#: model:ir.model.fields,help:pms.field_res_users__residence_street2 +msgid "Second street of the guest's residence" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__section_id +#: model:ir.model.fields.selection,name:pms.selection__folio_sale_line__display_type__line_section +msgid "Section" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__access_token +#: model:ir.model.fields,field_description:pms.field_pms_folio__access_token +#: model:ir.model.fields,field_description:pms.field_pms_reservation__access_token +msgid "Security Token" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_amenity__pms_amenity_type_id +msgid "Segment the amenities by categories (multimedia, comfort, etc ...)" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__segmentation_ids +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__segmentation_ids +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__segmentation_ids +#: model:ir.model.fields,field_description:pms.field_pms_folio__segmentation_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__segmentation_ids +msgid "Segmentation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__segmentation_ids +msgid "Segmentation tags to classify checkin partners" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__segmentation_ids +msgid "Segmentation tags to classify folios" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__segmentation_ids +msgid "Segmentation tags to classify reservations" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Segmentation..." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__document_type +msgid "Select a valid document type" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__categ_id +msgid "Select category for the current product" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.wizard_folio_changes_view_form +msgid "Selected reservations with different dates" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__num_rooms_selected +msgid "Selected rooms" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__invoice_warn +#: model:ir.model.fields,help:pms.field_pms_property__picking_warn +#: model:ir.model.fields,help:pms.field_pms_property__sale_warn +#: model:ir.model.fields,help:pms.field_pms_room_type__sale_line_warn +msgid "" +"Selecting the \"Warning\" option will notify user with the message, " +"Selecting \"Blocking Message\" will throw an exception with the message and " +"block the flow. The Message has to be written in the next field." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__self +msgid "Self" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Send Cancellation Email" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Send Confirmation Email" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Send Exit Email" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folio_invitations +msgid "Send Invitation" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "Send Mail " +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Send Modification Email" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__sequence +#: model:ir.model.fields,field_description:pms.field_pms_folio__sequence +#: model:ir.model.fields,field_description:pms.field_pms_room__sequence +#: model:ir.model.fields,field_description:pms.field_pms_room_type__sequence +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class__sequence +#: model:ir.model.fields,field_description:pms.field_pms_service__sequence +#: model:ir.model.fields,field_description:pms.field_pms_team_member__sequence +#: model:ir.model.fields,field_description:pms.field_pms_ubication__sequence +msgid "Sequence" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__sequence +msgid "Sequence used to form the name of the folio" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Sequences" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__service_ids +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__service +#: model:ir.model.fields,field_description:pms.field_pms_service__product_id +#: model:ir.model.fields.selection,name:pms.selection__pms_massive_changes_wizard__apply_pricelists_on__service +msgid "Service" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__service_order +msgid "Service Id" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_form +msgid "Service Line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__service_line_ids +#: model:ir.model.fields,field_description:pms.field_pms_service__service_line_ids +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Service Lines" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__service_id +msgid "Service Reference" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_service_line__service_id +msgid "Service Room" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_service_line +msgid "Service by day" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_service__name +#: model:ir.model.fields,help:pms.field_pms_service__name +msgid "Service description" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service_line__service_id +msgid "Service identifier" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__state +msgid "Service status, it corresponds with folio status" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_pms_service_line_form +#: model:ir.actions.act_window,name:pms.action_pms_services_form +#: model:ir.model.fields,field_description:pms.field_account_bank_statement_line__service_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__service_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__service_ids +#: model:ir.model.fields,field_description:pms.field_wizard_folio_changes__service_ids +#: model:ir.model.fields,field_description:pms.field_wizard_payment_folio__service_ids +#: model:ir.ui.menu,name:pms.menu_services_pms +#: model:ir.ui.menu,name:pms.pms_services_menu +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_graph +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_pivot +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_calendar +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation_detail +msgid "Services" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_service_line +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "Services By Day" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "Services NOT included in the room reservation price" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__wizard_folio_changes__modification_type__services +msgid "Services Prices" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__price_services +msgid "Services Total" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_service +msgid "Services and its charges" +msgstr "" + +#. module: pms +#: model:ir.ui.menu,name:pms.menu_pms_service_line +msgid "Services by Day" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__service_ids +msgid "" +"Services detail provide to customer and it will include in main Invoice." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__services_discount +msgid "Services discount" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__services_discount +msgid "Services discount (€)" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_bank_statement_line__service_ids +msgid "Services in which the Account Bank Statement Lines are included" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "Services included in the room reservation price" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service__board_service_line_ids +#: model:ir.model.fields,help:pms.field_pms_board_service_room_type__board_service_line_ids +msgid "Services included in this Board Service" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_board_service_line +msgid "Services on Board Service included" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_board_service_room_type_line +msgid "Services on Board Service included in Room" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__overnight_room +#: model:ir.model.fields,help:pms.field_pms_reservation_line__overnight_room +#: model:ir.model.fields,help:pms.field_pms_room_type__overnight_room +#: model:ir.model.fields,help:pms.field_pms_room_type_class__overnight +msgid "Set False if if these types of spaces are not used for overnight stays" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "Set to Done" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Settings" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__service_id +msgid "Sevice included in folio sale line" +msgstr "" + +#. module: pms +#: model:pms.amenity,name:pms.pms_amenity_shampoo_and_soap +msgid "Shampoo and Soap" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__partner_share +msgid "Share Partner" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__shared_folio +msgid "Shared Folio" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_form +msgid "Shared Room" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room__short_name +msgid "Short Name" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__pms_property_code +msgid "Short name property" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_board_service__show_detail_report +msgid "Show Detail Report" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__show_on_hand_qty_status_button +msgid "Show On Hand Qty Status Button" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +msgid "Show all checkins for Tomorrow" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +msgid "Show all checkins for enter tomorrow" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +msgid "Show all future checkins" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Show all reservations for which date enter is before than 14 days" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Show all reservations for which date enter is before than 7 days" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "" +"Show all reservations for which date enter is before than aprox. 1 month" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service_line__auto_qty +msgid "Show if the day qty was calculated automatically" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__sign_on +msgid "Sign on" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__signature +msgid "Signature" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__signature +msgid "Signature of the guest" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__signup_expiration +msgid "Signup Expiration" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__signup_token +msgid "Signup Token" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__signup_type +msgid "Signup Token Type" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__signup_valid +msgid "Signup Token is Valid" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__signup_url +msgid "Signup URL" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__sii_enabled +msgid "Sii Enabled" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.report_invoice_document +msgid "Simplified Invoice" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__journal_simplified_invoice_id +msgid "Simplified Invoice Journal" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_account_bank_statement_line__is_simplified_invoice +#: model:ir.model.fields,field_description:pms.field_account_journal__is_simplified_invoice +#: model:ir.model.fields,field_description:pms.field_account_move__is_simplified_invoice +#: model:ir.model.fields,field_description:pms.field_account_payment__is_simplified_invoice +msgid "Simplified invoice" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__sii_simplified_invoice +msgid "Simplified invoices in SII?" +msgstr "" + +#. module: pms +#: model:pms.room.type,name:pms.pms_room_type_single +#: model:product.product,name:pms.pms_room_type_single_product_product +msgid "Single" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__sales_count +msgid "Sold" +msgstr "" + +#. module: pms +#: code:addons/pms/models/res_users.py:0 +#, python-format +msgid "Some properties do not belong to the allowed companies" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_product_pricelist_item__board_service_room_type_id +msgid "Specify a Board services on Room Types." +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_cancelation_rule__apply_on_late__days +#: model:ir.model.fields.selection,name:pms.selection__pms_cancelation_rule__apply_on_noshow__days +msgid "Specify days" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation_split_join_swap_wizard__operation__split +#: model_terms:ir.ui.view,arch_db:pms.reservation_wizard +msgid "Split reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__splitted +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Splitted" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_booking_duplicate__reservation_type__staff +#: model:ir.model.fields.selection,name:pms.selection__pms_booking_engine__reservation_type__staff +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__reservation_type__staff +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__reservation_type__staff +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "Staff" +msgstr "" + +#. module: pms +#: code:addons/pms/controllers/pms_portal.py:0 +#, python-format +msgid "Stage" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__cost_method +msgid "" +"Standard Price: The products are valued at their standard cost defined on the product.\n" +" Average Cost (AVCO): The products are valued at weighted average cost.\n" +" First In First Out (FIFO): The products are valued supposing those that enter the company first will also leave it first.\n" +" " +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_product_pricelist_item__date_start_consumption +msgid "Start Date Consumption" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_engine__start_date +msgid "Start date for creation of reservations and folios" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_product_pricelist_item__date_start_consumption +msgid "Start date to apply daily pricelist items" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__state_id +#: model:ir.model.fields,field_description:pms.field_pms_reservation__state +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__state +#: model:ir.model.fields,field_description:pms.field_pms_service__state +#: model:ir.model.fields,field_description:pms.field_res_partner__state_id +#: model:ir.model.fields,field_description:pms.field_res_users__state_id +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +#: model_terms:ir.ui.view,arch_db:pms.res_partner_view_form +msgid "State" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__invoice_status +msgid "" +"State in which the service is with respect to invoices.It can be 'invoiced'," +" 'to_invoice' or 'no'" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__residence_state_id +#: model:ir.model.fields,field_description:pms.field_pms_property__residence_state_id +#: model:ir.model.fields,field_description:pms.field_res_partner__residence_state_id +#: model:ir.model.fields,field_description:pms.field_res_users__residence_state_id +msgid "State of residence" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__residence_state_id +msgid "State of the guest's residence" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_line__state +msgid "State of the reservation line." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__statement_line_ids +msgid "Statement lines" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__state +#: model:ir.model.fields,field_description:pms.field_pms_folio__state +msgid "Status" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__activity_state +#: model:ir.model.fields,help:pms.field_pms_folio__activity_state +#: model:ir.model.fields,help:pms.field_pms_property__activity_state +#: model:ir.model.fields,help:pms.field_pms_reservation__activity_state +#: model:ir.model.fields,help:pms.field_pms_room_type__activity_state +msgid "" +"Status based on activities\n" +"Overdue: Due date is already passed\n" +"Today: Activity date is today\n" +"Planned: Future activities." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__state +msgid "Status of the checkin partner regarding the reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__stock_move_ids +msgid "Stock Move" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__picking_warn +msgid "Stock Picking" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__stock_quant_ids +msgid "Stock Quant" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__stock_valuation_layer_ids +msgid "Stock Valuation Layer" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__residence_street +#: model:ir.model.fields,field_description:pms.field_pms_property__street +#: model:ir.model.fields,field_description:pms.field_res_partner__street +#: model:ir.model.fields,field_description:pms.field_res_users__street +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Street" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +#: model_terms:ir.ui.view,arch_db:pms.res_partner_view_form +msgid "Street 2..." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__residence_street +#: model:ir.model.fields,field_description:pms.field_res_partner__residence_street +#: model:ir.model.fields,field_description:pms.field_res_users__residence_street +msgid "Street of residence" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__residence_street +#: model:ir.model.fields,help:pms.field_pms_property__residence_street +#: model:ir.model.fields,help:pms.field_res_partner__residence_street +#: model:ir.model.fields,help:pms.field_res_users__residence_street +msgid "Street of the guest's residence" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +#: model_terms:ir.ui.view,arch_db:pms.res_partner_view_form +msgid "Street..." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__residence_street2 +#: model:ir.model.fields,field_description:pms.field_pms_property__street2 +#: model:ir.model.fields,field_description:pms.field_res_partner__street2 +#: model:ir.model.fields,field_description:pms.field_res_users__street2 +msgid "Street2" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__service_line_ids +msgid "Subservices included in folio sale line service" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__service_line_ids +msgid "Subservices included in this service" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__price_subtotal +#: model:ir.model.fields,field_description:pms.field_pms_reservation__price_subtotal +#: model:ir.model.fields,field_description:pms.field_pms_service__price_subtotal +#: model:ir.model.fields,field_description:pms.field_pms_service_line__price_day_subtotal +msgid "Subtotal" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__price_subtotal +#: model:ir.model.fields,help:pms.field_pms_reservation__price_subtotal +#: model:ir.model.fields,help:pms.field_pms_service__price_subtotal +#: model:ir.model.fields,help:pms.field_pms_service_line__price_day_subtotal +msgid "Subtotal price without taxes" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.wizard_folio_changes_view_form +msgid "Sunday" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__supplier_rank +msgid "Supplier Rank" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_amenity_view_form +msgid "Supplier Taxes" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_amenity_view_form +msgid "Suppliers" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.reservation_wizard +msgid "Swap reservation rooms" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation_split_join_swap_wizard__operation__swap +msgid "Swap rooms" +msgstr "" + +#. module: pms +#. openerp-web +#: code:addons/pms/static/src/xml/pms_base_templates.xml:0 +#, python-format +msgid "Switch to this property" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_ir_config_parameter +msgid "System Parameter" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "TRAVELER'S DOCUMENT" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +msgid "Tables Detail" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__category_id +msgid "Tags" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__vat +#: model_terms:ir.ui.view,arch_db:pms.report_folio_document +msgid "Tax ID" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__tax_ids +#: model:ir.model.fields,field_description:pms.field_pms_folio__amount_tax +#: model:ir.model.fields,field_description:pms.field_pms_reservation__tax_ids +#: model:ir.model.fields,field_description:pms.field_pms_service__tax_ids +#: model:ir.model.fields,field_description:pms.field_pms_service_line__tax_ids +#: model_terms:ir.ui.view,arch_db:pms.report_folio_document +msgid "Taxes" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__price_tax +#: model:ir.model.fields,field_description:pms.field_pms_service__price_tax +#: model:ir.model.fields,field_description:pms.field_pms_service_line__price_day_tax +msgid "Taxes Amount" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__tax_ids +msgid "Taxes applied in the folio sale line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__tax_ids +msgid "Taxes applied in the reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__tax_ids +msgid "Taxes applied in the service" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service_line__tax_ids +msgid "Taxes applied in the service line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_advance_payment_inv__deposit_taxes_id +msgid "Taxes used for deposits" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__user_id +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Team Leader" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__member_ids +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Team Members" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__folio_sequence +msgid "Techinal field to get reservation name" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_move_line__name_changed_by_user +msgid "" +"Techinal field to know if the name was set manually by the user\n" +" or by the system. If the name was set manually, the system will not\n" +" change it when the qty days are changed" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__show_update_pricelist +msgid "" +"Technical Field, True if the pricelist was changed;\n" +" this will then display a recomputation button" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__valid_product_template_attribute_line_ids +msgid "Technical compute" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__document_id +msgid "Technical field" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__display_type +msgid "Technical field for UX purpose." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__payment_multi +msgid "Technical field for manage payments with multiple folios assigned" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__preconfirm +msgid "Technical field that indicates the reservation is not comfirm yet" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__no_auto_add_lines +msgid "" +"Technical field to avoid add service lines to service\n" +" automatically when creating a new service. It is used when\n" +" creating a new service with lines in vals\n" +" " +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__days_to_checkin +msgid "" +"Technical field to facilitate\n" +" filtering by dates related to checkin" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__days_to_checkout +msgid "" +"Technical field to facilitate\n" +" filtering by dates related to checkout" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_bank_statement_line__is_simplified_invoice +#: model:ir.model.fields,help:pms.field_account_move__is_simplified_invoice +#: model:ir.model.fields,help:pms.field_account_payment__is_simplified_invoice +msgid "Technical field to know if the invoice is simplified" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__is_origin_channel_check_visible +msgid "Technical field to make visible update origin channel check" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__allowed_checkin +msgid "" +"Technical field, Indicates if there isn't a checkin_partner dataOnly can be " +"true if checkin is today or was in the past" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__allowed_cancel +msgid "" +"Technical field, Indicates that reservation can be cancelled,that happened " +"when state is 'cancel', 'done', or 'departure_delayed'" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__allowed_checkout +msgid "" +"Technical field, Indicates that reservation is ready for checkoutonly can be" +" true if reservation state is 'onboard' or departure_delayedand checkout is " +"today or will be in the future" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__pricelist_id +msgid "" +"Technical field. Used for searching on pricelists, not stored in database." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__stock_move_ids +#: model:ir.model.fields,help:pms.field_pms_room_type__stock_quant_ids +msgid "Technical: used to compute quantities." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__note +msgid "Terms and conditions" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_end +msgid "" +"Thank you!\n" +" Your check-in has been successful." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_res_partner_id_number__name +msgid "The ID itself. For example, Driver License number of this person" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "The Property and Sale Channel Origin are mandatory in the reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__vat +msgid "" +"The Tax Identification Number. Complete it if the contact is subjected to " +"government taxes. Used in some legal statements." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__administrative_user_id +msgid "The administrative manager in the folio" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_bank_statement_line__origin_agency_id +#: model:ir.model.fields,help:pms.field_account_move__origin_agency_id +#: model:ir.model.fields,help:pms.field_account_move_line__origin_agency_id +#: model:ir.model.fields,help:pms.field_account_payment__origin_agency_id +msgid "The agency where the folio account move originates" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__origin_agency_id +msgid "The agency where the folio sale line originates" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__invoice_count +msgid "The amount of invoices in out invoice and out refund status" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__pending_amount +msgid "The amount that remains to be paid" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__folio_pending_amount +msgid "The amount that remains to be paid from folio" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__untaxed_amount_to_invoice +msgid "The amount to invoice" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__untaxed_amount_invoiced +msgid "The amount to invoice without taxes in the line of folio" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__analytic_account_id +msgid "The analytic account related to a folio." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability_plan_rule__availability_plan_id +msgid "The availability plan that include the Availabilty Rule" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_room.py:0 +#, python-format +msgid "" +"The capacity of the room must be greater than 0." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__checkin_partner_ids +msgid "The checkin partners on a folio" +msgstr "" + +#. module: pms +#: code:addons/pms/models/res_users.py:0 +#, python-format +msgid "The chosen property is not in the allowed properties for this user" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__closure_reason_id +msgid "The closure reason for a closure room" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__company_id +msgid "The company for folio" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__company_id +msgid "The company in the folio sale line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__company_id +msgid "The company that owns or operates this property." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__currency_id +msgid "The currency for the folio" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__currency_id +msgid "The currency of the property location" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__currency_id +msgid "The currency used in relation to the folio" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__currency_id +msgid "The currency used in relation to the pricelist" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service_line__currency_id +msgid "The currency used in relation to the service where it's included" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_line__date +msgid "The date of the reservation in reservation line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__invoicing_month_day +#: model:ir.model.fields,help:pms.field_res_partner__invoicing_month_day +#: model:ir.model.fields,help:pms.field_res_users__invoicing_month_day +msgid "The day of the month to invoice" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__default_pricelist_id +msgid "The default pricelist used in this property." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__fiscal_position_id +msgid "The fiscal position depends on the location of the client" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__property_account_position_id +msgid "" +"The fiscal position determines the taxes/accounts used for this contact." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_advance_payment_inv__fixed_amount +msgid "The fixed amount to be invoiced in advance, taxes excluded." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__partner_id +msgid "The folio customer" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_move_line__folio_line_ids +msgid "The folio lines in the account move lines" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__folio_id +msgid "The folio where the reservations are included" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__untaxed_amount_to_invoice +msgid "The invoiced amount without taxes in the line of the folio" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__invoicing_policy +#: model:ir.model.fields,help:pms.field_res_partner__invoicing_policy +#: model:ir.model.fields,help:pms.field_res_users__invoicing_policy +msgid "" +"The invoicing policy of the partner,\n" +" set Property to user the policy configured in the Property" +msgstr "" + +#. module: pms +#: code:addons/pms/models/account_journal.py:0 +#, python-format +msgid "The journal %s is used for normal invoices in the properties: %s" +msgstr "" + +#. module: pms +#: code:addons/pms/models/account_journal.py:0 +#, python-format +msgid "The journal %s is used for simplified invoices in the properties: %s" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__manager_user_id +msgid "The main manager in the folio" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room__capacity +msgid "The maximum number of people that can occupy a room" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_team_member__pms_role +msgid "" +"The member role in the organizationIt can be 'Reception', 'Revenue', " +"'Administrative', or 'Manager'" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_move_line__move_id +msgid "The move of this entry line." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_sale_channel__name +msgid "The name of the sale channel" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_room_closure_reason__name +msgid "The name that identifies the room closure reason" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__pos_order_count +msgid "The number of point of sales orders related to this customer" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__total_rooms_count +msgid "The number of rooms in a room type" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__count_rooms_pending_arrival +msgid "The number of rooms left to occupy." +msgstr "" + +#. module: pms +#: code:addons/pms/models/folio_sale_line.py:0 +#, python-format +msgid "The ordered quantity has been updated." +msgstr "" + +#. module: pms +#: code:addons/pms/models/ir_config_parameter.py:0 +#, python-format +msgid "The parameter Advanced price rules cannot be modified" +msgstr "" + +#. module: pms +#: code:addons/pms/models/res_partner.py:0 +#, python-format +msgid "The partner %s cannot be deleted" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__has_unreconciled_entries +msgid "" +"The partner has at least one unreconciled debit and credit since last time " +"the invoices & payments matching was performed." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__reference +msgid "The payment communication of this sale order." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_advance_payment_inv__amount +msgid "The percentage of amount to be invoiced in advance, taxes excluded." +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "" +"The period to create this invoice is locked. Please contact your " +"administrator to unlock it." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_line__price +msgid "The price in a reservation line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__amount_untaxed +msgid "The price without taxes on a folio" +msgstr "" + +#. module: pms +#: code:addons/pms/wizards/folio_make_invoice_advance.py:0 +#, python-format +msgid "" +"The product used to invoice a down payment should\n" +" be of type 'Service'.\n" +" Please use another product or update this product." +msgstr "" + +#. module: pms +#: code:addons/pms/wizards/folio_make_invoice_advance.py:0 +#, python-format +msgid "" +"The product used to invoice a down payment should\n" +" have an invoice policy set to \"Ordered quantities\".\n" +" Please update your deposit product to be able\n" +" to create a deposit invoice." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_res_users__pms_property_ids +msgid "The properties allowed for this user" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_bank_statement_line__pms_property_id +#: model:ir.model.fields,help:pms.field_account_move__pms_property_id +#: model:ir.model.fields,help:pms.field_account_payment__pms_property_id +msgid "The property associated to the account move" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__pms_property_id +msgid "The property for folios" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_res_users__pms_property_id +msgid "The property that is selected within those allowed for the user" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__qty_to_invoice +msgid "" +"The quantity to invoice. If the invoice policy is order, the quantity to " +"invoice is calculated from the ordered quantity. Otherwise, the quantity " +"delivered is used." +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_not_checkin +msgid "" +"The quick registration system is not available for this reservation.
\n" +" If you have any questions, you can contact us:

Phone:" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__user_id +#: model:ir.model.fields,help:pms.field_pms_reservation__user_id +msgid "The reception manager in the folio" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_payment__origin_reference +msgid "The reference of the payment origin" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "The reservation must be canceled by action: action_cancel" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "The reservation must be confirmed by action: action_confirm" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "The reservation type must be the same for all reservations in folio" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_room.py:0 +#, python-format +msgid "" +"The reservation units are required on shared rooms." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__revenue_user_id +msgid "The revenue manager in the folio" +msgstr "" + +#. module: pms +#: code:addons/pms/wizards/wizard_split_join_swap_reservation.py:0 +#, python-format +msgid "The room does not exist" +msgstr "" + +#. module: pms +#: code:addons/pms/wizards/wizard_split_join_swap_reservation.py:0 +#, python-format +msgid "The room is not available" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_line__room_id +msgid "The room of a reservation. " +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__sale_channel_id +#: model:ir.model.fields,help:pms.field_res_partner__sale_channel_id +#: model:ir.model.fields,help:pms.field_res_users__sale_channel_id +msgid "The sale channel of the partner" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__lst_price +msgid "" +"The sale price is managed from the product template. Click on the 'Configure" +" Variants' button to set the extra attribute prices." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__section_id +msgid "The section of the folio sale line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__folio_sequence_id +msgid "The sequence that formed the name of the folio." +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_room.py:0 +#, python-format +msgid "The short name can't contain more than 4 characters" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__payment_state +msgid "The state of the payment" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__state +msgid "" +"The state of the reservation. It can be 'Pre-reservation', 'Pending " +"arrival', 'On Board', 'Out', 'Cancelled', 'Arrival Delayed' or 'Departure " +"Delayed'" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__folio_payment_state +msgid "The status of the folio payment" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__state +msgid "The status of the folio related with folio sale line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__invoice_status +msgid "" +"The status of the invoices in folio. Can be 'invoiced', 'to_invoice' or " +"'no'." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__property_stock_customer +msgid "" +"The stock location used as destination when sending goods to this contact." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__property_stock_supplier +msgid "" +"The stock location used as source when receiving goods from this contact." +msgstr "" + +#. module: pms +#: code:addons/pms/models/account_move.py:0 +#, python-format +msgid "" +"The total amount of the simplified invoice is higher than the maximum amount" +" allowed for simplified invoices." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio_availability_wizard__price_total +#: model:ir.model.fields,help:pms.field_pms_reservation_duplicate__price_total +msgid "The total price in the folio" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__reservation_type +#: model:ir.model.fields,help:pms.field_pms_booking_engine__reservation_type +#: model:ir.model.fields,help:pms.field_pms_folio__reservation_type +msgid "" +"The type of the reservation. Can be 'Normal', 'Staff' or 'Out of Service'" +msgstr "" + +#. module: pms +#: code:addons/pms/wizards/folio_make_invoice_advance.py:0 +#, python-format +msgid "The value of the down payment amount must be positive." +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folio +msgid "There are currently no folios for your account." +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation +msgid "There are currently no reservations for your account." +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folio_invitations +msgid "There are currently no reservations in this folio for your account." +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "There are no checkins to print" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation_line.py:0 +#, python-format +msgid "There is no availability for the room type %s on %s" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "" +"There is nothing to invoice!\n" +"\n" +" Reason(s) of this behavior could be:\n" +" - You should deliver your products before invoicing them: Click on the \"truck\"\n" +" icon (top-right of your screen) and follow instructions.\n" +" - You should modify the invoicing policy of your product: Open the product,\n" +" go to the \"Sales tab\" and modify invoicing policy from \"delivered quantities\"\n" +" to \"ordered quantities\".\n" +" " +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_folio_error +msgid "There was an error processing this page." +msgstr "" + +#. module: pms +#: code:addons/pms/wizards/wizard_split_join_swap_reservation.py:0 +#, python-format +msgid "There's no reservations lines with provided room" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_availability__reservation_line_ids +#: model:ir.model.fields,help:pms.field_pms_reservation__reservation_line_ids +msgid "" +"They are the lines of the reservation into a reservation,they corresponds to" +" the nights" +msgstr "" + +#. module: pms +#: model:pms.ubication,name:pms.pms_ubication_3rd_floor +msgid "Third floor" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__property_account_payable_id +msgid "" +"This account will be used instead of the default one as the payable account " +"for the current partner" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__property_account_receivable_id +msgid "" +"This account will be used instead of the default one as the receivable " +"account for the current partner" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__force_update_origin +msgid "" +"This field is for force update in sale channel origin of folio and another " +"reservations" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__checkin_datetime +msgid "" +"This field is the day and time of arrival of the reservation.It is formed " +"with the checkin and arrival_hour fields" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__checkout_datetime +msgid "" +"This field is the day and time of departure of the reservation.It is formed " +"with the checkout and departure_hour fields" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__tz +msgid "This field is used to determine de timezone of the property." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__email_normalized +msgid "" +"This field is used to search on email address as the primary email field can" +" contain more than strictly an email address." +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "" +"This folio has payments assigned to multiple folios (through an invoice or directly).\n" +" The Folio will only be considered paid if all the folios with the same associated\n" +" payment are paid, or if a specific payment is assigned to this folio." +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_checkin_partner.py:0 +#, python-format +msgid "This guest is already registered in the room" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__price_extra +msgid "This is the sum of the extra price of all attributes" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__property_supplier_payment_term_id +msgid "" +"This payment term will be used instead of the default one for purchase " +"orders and vendor bills" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__property_payment_term_id +msgid "" +"This payment term will be used instead of the default one for sales orders " +"and customer invoices" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__property_product_pricelist +msgid "" +"This pricelist will be used, instead of the default one, for sales to the " +"current partner" +msgstr "" + +#. module: pms +#: code:addons/pms/models/folio_sale_line.py:0 +#, python-format +msgid "" +"This quantity was already invoiced. You must reduce the invoiced quantity " +"first." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_line__occupies_availability +msgid "This record is taken into account to calculate availability" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "This reservation cannot be cancelled" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "This reservation cannot be check out" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "" +"This reservation has other reservantions and/or services in the\n" +" folio, you can check it in the" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "" +"This reservation is part of a splitted reservation, you can try to\n" +" join the reservation here" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__property_stock_production +msgid "" +"This stock location will be used, instead of the default one, as the source " +"location for stock moves generated by manufacturing orders." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__property_stock_inventory +msgid "" +"This stock location will be used, instead of the default one, as the source " +"location for stock moves generated when you do an inventory." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room_type__responsible_id +msgid "" +"This user will be responsible of the next activities related to logistic " +"operations for this product." +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "This will update all unit prices based on the currently set pricelist." +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.wizard_folio_changes_view_form +msgid "Thursday" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__tz +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +msgid "Timezone" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__tz_offset +msgid "Timezone offset" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__title +msgid "Title" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_massive_changes_wizard__end_date +msgid "To" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__to_assign +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "To Assign" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__invoice_status__to_confirm +msgid "To Confirm" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__folio_sale_line__invoice_status__to_invoice +#: model:ir.model.fields.selection,name:pms.selection__pms_folio__invoice_status__to_invoice +#: model:ir.model.fields.selection,name:pms.selection__pms_reservation__invoice_status__to_invoice +#: model:ir.model.fields.selection,name:pms.selection__pms_service__invoice_status__to_invoice +msgid "To Invoice" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__qty_to_invoice +msgid "To Invoice Quantity" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__to_send_cancelation_mail +msgid "To Send Cancelation Mail" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__to_send_confirmation_mail +msgid "To Send Confirmation Mail" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__to_send_exit_mail +msgid "To Send Exit Mail" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__to_send_modification_mail +msgid "To Send Modification Mail" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__to_weight +msgid "To Weigh With Scale" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "To be paid" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_folio_search +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_search +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_search +msgid "To enter" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_search +msgid "To invoice" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__partner_name +msgid "To whom the room is assigned" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__end_date +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__checkout +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__checkout +msgid "To:" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "Today" +msgstr "" + +#. module: pms +#: model:pms.amenity.type,name:pms.pms_amenity_type_toiletries +msgid "Toiletries" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_service_line_view_search +msgid "Tomorrow" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__price_total +#: model:ir.model.fields,field_description:pms.field_pms_folio__amount_total +#: model:ir.model.fields,field_description:pms.field_pms_reservation__price_total +#: model:ir.model.fields,field_description:pms.field_pms_service__price_total +#: model:ir.model.fields,field_description:pms.field_pms_service_line__price_day_total +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_tree +#: model_terms:ir.ui.view,arch_db:pms.portal_my_folio +#: model_terms:ir.ui.view,arch_db:pms.portal_my_reservation +msgid "Total" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__total_invoiced +msgid "Total Invoiced" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__debit +msgid "Total Payable" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_tree +msgid "Total Pending" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__total_price_folio +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__total_price_folio +msgid "Total Price" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__credit +msgid "Total Receivable" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__total_rooms_count +msgid "Total Rooms Count" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__price_tax +msgid "Total Tax" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_tree +msgid "Total amount" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "" +"Total amount\n" +" (Reservation Card):" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "Total amount (Reservation Card):" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__credit +msgid "Total amount this customer owes you." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__amount_total +msgid "Total amount to be paid" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__debit +msgid "Total amount you have to pay to this vendor." +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__price_tax +#: model:ir.model.fields,help:pms.field_pms_reservation__price_tax +msgid "Total of taxes in a reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_service__price_tax +msgid "Total of taxes in service" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio_availability_wizard__price_total +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__price_total +msgid "Total price" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__price_services +msgid "Total price from services of a reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_booking_duplicate__total_price_folio +#: model:ir.model.fields,help:pms.field_pms_booking_engine__total_price_folio +msgid "Total price of folio with taxes" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__price_room_services_set +msgid "Total price of room and services" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__price_total +#: model:ir.model.fields,help:pms.field_pms_reservation__price_total +#: model:ir.model.fields,help:pms.field_pms_service__price_total +#: model:ir.model.fields,help:pms.field_pms_service_line__price_day_total +msgid "Total price with taxes" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__service_type +msgid "Track Service" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__tracking +msgid "Tracking" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__transaction_ids +msgid "Transactions" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "Traveler's signature" +msgstr "" + +#. module: pms +#: model:ir.actions.report,name:pms.action_traveller_report +msgid "Traveller Report" +msgstr "" + +#. module: pms +#: model:pms.room.type,name:pms.pms_room_type_triple +#: model:product.product,name:pms.pms_room_type_triple_product_product +msgid "Triple" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_amenity__is_add_code_room_name +msgid "" +"True if the Internal Reference should appear in the display name of the " +"rooms" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service__show_detail_report +msgid "True if you want that board service detail to be shown on the report" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.wizard_folio_changes_view_form +msgid "Tuesday" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__reservation_type +#: model:ir.model.fields,field_description:pms.field_pms_booking_engine__reservation_type +#: model:ir.model.fields,field_description:pms.field_pms_folio__reservation_type +#: model_terms:ir.ui.view,arch_db:pms.traveller_report +msgid "Type" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__reservation_type +msgid "Type of reservations. It can be 'normal', 'staff' or 'out of service" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_sale_channel__channel_type +msgid "" +"Type of sale channel; it can be 'direct'(if there isno intermediary) or " +"'indirect'(if there areintermediaries between partner and property" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__activity_exception_decoration +#: model:ir.model.fields,help:pms.field_pms_folio__activity_exception_decoration +#: model:ir.model.fields,help:pms.field_pms_property__activity_exception_decoration +#: model:ir.model.fields,help:pms.field_pms_reservation__activity_exception_decoration +#: model:ir.model.fields,help:pms.field_pms_room_type__activity_exception_decoration +msgid "Type of the exception activity on record." +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Type your country here" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Type your nationality here" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Type your state here" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_precheckin_detail +msgid "Type your zip code here" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class__room_type_ids +msgid "Types" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_ubication +#: model:ir.model.fields,field_description:pms.field_pms_room__ubication_id +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_room_view_search +msgid "Ubication" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_ubication__name +#: model:ir.model.fields,help:pms.field_pms_ubication__name +msgid "Ubication Name" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.open_pms_ubication_form_tree +msgid "Ubication Structure" +msgstr "" + +#. module: pms +#: model:ir.ui.menu,name:pms.menu_open_pms_ubication_form_tree +msgid "Ubications" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_board_service__default_code +msgid "Unique Board Service identification code per property" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room__room_type_id +msgid "Unique room type for the rooms" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__price_unit +#: model:ir.model.fields,field_description:pms.field_pms_service_line__price_unit +#: model_terms:ir.ui.view,arch_db:pms.report_folio_document +msgid "Unit Price" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_folio_sale_line__price_unit +msgid "Unit Price of folio sale line" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__per_day +#: model:ir.model.fields,field_description:pms.field_product_product__per_day +#: model:ir.model.fields,field_description:pms.field_product_template__per_day +msgid "Unit increment per day" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__per_person +#: model:ir.model.fields,field_description:pms.field_product_product__per_person +#: model:ir.model.fields,field_description:pms.field_product_template__per_person +msgid "Unit increment per person" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__product_uom +#: model:ir.model.fields,field_description:pms.field_pms_room_type__uom_id +msgid "Unit of Measure" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__product_uom_category_id +msgid "Unit of Measure Category" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__uom_name +msgid "Unit of Measure Name" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_service_line__day_qty +#: model:pms.room.type,uom_name:pms.demo_pms_room_type_grand_suite +#: model:pms.room.type,uom_name:pms.demo_pms_room_type_junior_suite +#: model:pms.room.type,uom_name:pms.pms_room_type_conference_room +#: model:pms.room.type,uom_name:pms.pms_room_type_double +#: model:pms.room.type,uom_name:pms.pms_room_type_economic +#: model:pms.room.type,uom_name:pms.pms_room_type_parking +#: model:pms.room.type,uom_name:pms.pms_room_type_quadruple +#: model:pms.room.type,uom_name:pms.pms_room_type_single +#: model:pms.room.type,uom_name:pms.pms_room_type_triple +#: model:product.product,uom_name:pms.demo_pms_room_type_grand_suite_product_product +#: model:product.product,uom_name:pms.demo_pms_room_type_junior_suite_product_product +#: model:product.product,uom_name:pms.pms_room_type_conference_room_product_product +#: model:product.product,uom_name:pms.pms_room_type_double_product_product +#: model:product.product,uom_name:pms.pms_room_type_economic_product_product +#: model:product.product,uom_name:pms.pms_room_type_parking_product_product +#: model:product.product,uom_name:pms.pms_room_type_quadruple_product_product +#: model:product.product,uom_name:pms.pms_room_type_single_product_product +#: model:product.product,uom_name:pms.pms_room_type_triple_product_product +#: model:product.product,uom_name:pms.pms_service_breakfast_buffet +#: model:product.product,uom_name:pms.pms_service_dinner +#: model:product.product,uom_name:pms.pms_service_extra_bed +#: model:product.product,uom_name:pms.pms_service_free_bar +#: model:product.product,uom_name:pms.pms_service_late_checkout +#: model:product.product,uom_name:pms.pms_service_lunch +#: model:product.template,uom_name:pms.pms_service_breakfast_buffet_product_template +#: model:product.template,uom_name:pms.pms_service_dinner_product_template +#: model:product.template,uom_name:pms.pms_service_extra_bed_product_template +#: model:product.template,uom_name:pms.pms_service_free_bar_product_template +#: model:product.template,uom_name:pms.pms_service_late_checkout_product_template +#: model:product.template,uom_name:pms.pms_service_lunch_product_template +msgid "Units" +msgstr "" + +#. module: pms +#: model:ir.model.fields.selection,name:pms.selection__pms_checkin_partner__state__dummy +msgid "Unkown Guest" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__message_unread +#: model:ir.model.fields,field_description:pms.field_pms_folio__message_unread +#: model:ir.model.fields,field_description:pms.field_pms_property__message_unread +#: model:ir.model.fields,field_description:pms.field_pms_reservation__message_unread +#: model:ir.model.fields,field_description:pms.field_pms_room_type__message_unread +msgid "Unread Messages" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__message_unread_counter +#: model:ir.model.fields,field_description:pms.field_pms_folio__message_unread_counter +#: model:ir.model.fields,field_description:pms.field_pms_property__message_unread_counter +#: model:ir.model.fields,field_description:pms.field_pms_reservation__message_unread_counter +#: model:ir.model.fields,field_description:pms.field_pms_room_type__message_unread_counter +msgid "Unread Messages Counter" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "Unsupported operator %s for searching on date" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_folio__amount_untaxed +msgid "Untaxed Amount" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__untaxed_amount_to_invoice +msgid "Untaxed Amount To Invoice" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_folio_sale_line__untaxed_amount_invoiced +msgid "Untaxed Invoiced Amount" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "UoM" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "Update Prices" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__force_update_origin +msgid "Update Sale Channel Origin" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_res_company__url_advert +msgid "Url Advert" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_res_company__url_advert +msgid "Url to identify the ad" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__barcode +msgid "Use a barcode to identify this contact." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_reservation__overnight_room +#: model:ir.model.fields,field_description:pms.field_pms_reservation_line__overnight_room +#: model:ir.model.fields,field_description:pms.field_pms_room_type__overnight_room +#: model:ir.model.fields,field_description:pms.field_pms_room_type_class__overnight +msgid "Use for overnight stays" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_journal__allowed_pms_payments +msgid "Use to pay for reservations" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_account_journal__is_simplified_invoice +msgid "Use to simplified invoice" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_booking_duplicate__used_room_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation_duplicate__used_room_ids +msgid "Used Rooms" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_res_partner_category__is_used_in_checkin +msgid "Used in checkin" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__shared_folio +msgid "" +"Used to notify is the reservation folio has other reservations/services" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__aeat_identification_type +msgid "" +"Used to specify an identification type to send to SII. Normally for sending " +"national and export invoices to SII where the customer country is not Spain," +" it would calculate an identification type of 04 if the VAT field is filled " +"and 06 if it was not. This field is to specify types of 03 through 05, in " +"the event that the customer doesn't identify with a foreign VAT and instead " +"with their passport or residential certificate. If there is no value it will" +" work as before." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_team_member__user_id +msgid "User Member" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_res_users +msgid "Users" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__currency_id +msgid "Utility field to express amount currency" +msgstr "" + +#. module: pms +#: model:room.closure.reason,name:pms.pms_room_closure_reason_vip_privacy +msgid "VIP Privacy" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__valid_product_template_attribute_line_ids +msgid "Valid Product Attribute Lines" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_res_partner_id_number__valid_from +msgid "Valid from" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_res_partner_id_number__valid_from +msgid "Validation period stating date." +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_num_rooms_selection__value +msgid "Value" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__value_svl +msgid "Value Svl" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__image_variant_1920 +msgid "Variant Image" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__image_variant_1024 +msgid "Variant Image 1024" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__image_variant_128 +msgid "Variant Image 128" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__image_variant_256 +msgid "Variant Image 256" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__image_variant_512 +msgid "Variant Image 512" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__price_extra +msgid "Variant Price Extra" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__variant_seller_ids +msgid "Variant Seller" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.report_invoice_document +msgid "Vendor Bill" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.report_invoice_document +msgid "Vendor Credit Note" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__property_stock_supplier +msgid "Vendor Location" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__property_supplier_payment_term_id +msgid "Vendor Payment Terms" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__supplier_taxes_id +msgid "Vendor Taxes" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__seller_ids +msgid "Vendors" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "View Customer" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.booking_duplicate +msgid "View Folios" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__volume +msgid "Volume" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__volume_uom_name +msgid "Volume unit of measure label" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__warehouse_id +msgid "Warehouse" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_service.py:0 +#, python-format +msgid "Warning for %s" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__cardex_warning +msgid "Warning in Cardex" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__website +msgid "Website Link" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__website_message_ids +#: model:ir.model.fields,field_description:pms.field_pms_folio__website_message_ids +#: model:ir.model.fields,field_description:pms.field_pms_property__website_message_ids +#: model:ir.model.fields,field_description:pms.field_pms_reservation__website_message_ids +#: model:ir.model.fields,field_description:pms.field_pms_room_type__website_message_ids +msgid "Website Messages" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__website_message_ids +#: model:ir.model.fields,help:pms.field_pms_folio__website_message_ids +#: model:ir.model.fields,help:pms.field_pms_property__website_message_ids +#: model:ir.model.fields,help:pms.field_pms_reservation__website_message_ids +#: model:ir.model.fields,help:pms.field_pms_room_type__website_message_ids +msgid "Website communication history" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.wizard_folio_changes_view_form +msgid "Wednesday" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__weight +msgid "Weight" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_room_type__weight_uom_name +msgid "Weight unit of measure label" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__force_nothing_to_invoice +msgid "" +"When you set this field, the folio will be considered as nothin to invoice, " +"even when there may be ordered quantities pending to invoice." +msgstr "" + +#. module: pms +#: model:pms.amenity,name:pms.pms_amenity_wi_fi +msgid "Wi-Fi" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_advanced_filters_wizard +msgid "Wizard for advanced filters" +msgstr "" + +#. module: pms +#: model:ir.model,name:pms.model_pms_massive_changes_wizard +msgid "Wizard for massive changes on Availability Plans & Pricelists." +msgstr "" + +#. module: pms +#: code:addons/pms/wizards/pms_booking_duplicate.py:0 +#, python-format +msgid "" +"You can not create a new folio because there are rooms already occupied.\n" +" Please, check the rooms marked in red and try again." +msgstr "" + +#. module: pms +#: code:addons/pms/models/folio_sale_line.py:0 +#, python-format +msgid "" +"You cannot delete a sale order line once a invoice has been created from it." +msgstr "" + +#. module: pms +#: model:ir.model.constraint,message:pms.constraint_pms_room_room_property_unique +msgid "" +"You cannot have more than one room with the same name in the same property" +msgstr "" + +#. module: pms +#: model:ir.model.constraint,message:pms.constraint_pms_room_room_short_name_unique +msgid "" +"You cannot have more than one room with the same short name in the same " +"property" +msgstr "" + +#. module: pms +#: code:addons/pms/models/folio_sale_line.py:0 +#, python-format +msgid "" +"You cannot reduce the invoiced quantity below\n" +" the quantity already invoiced." +msgstr "" + +#. module: pms +#: code:addons/pms/models/account_move.py:0 +#, python-format +msgid "" +"You cannot validate this invoice. Please check the partner has the complete" +" information required." +msgstr "" + +#. module: pms +#: code:addons/pms/wizards/wizard_advanced_filters.py:0 +#, python-format +msgid "You must add filters to perform the search" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "You must assign a customer name" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "" +"You must select a cancelation template in the email configuration menu of " +"the property" +msgstr "" + +#. module: pms +#: code:addons/pms/wizards/wizard_several_partners.py:0 +#, python-format +msgid "You must select a client to be able to add it to the reservation " +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "" +"You must select a confirmation template in the email configuration menu of " +"the property" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "" +"You must select a exit template in the email configuration menu of the " +"property" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_folio.py:0 +#, python-format +msgid "" +"You must select a modification template in the email configuration menu of " +"the property" +msgstr "" + +#. module: pms +#: model:mail.template,subject:pms.cancelled_reservation_email +msgid "Your reservation in ${object.pms_property_id.name} has been cancelled" +msgstr "" + +#. module: pms +#: model:mail.template,subject:pms.modified_reservation_email +msgid "Your reservation in ${object.pms_property_id.name} has been modified" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_checkin_partner_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_property_views_form +#: model_terms:ir.ui.view,arch_db:pms.res_partner_view_form +msgid "ZIP" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__residence_zip +#: model:ir.model.fields,field_description:pms.field_pms_property__zip +#: model:ir.model.fields,field_description:pms.field_res_partner__zip +#: model:ir.model.fields,field_description:pms.field_res_users__zip +msgid "Zip" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__residence_zip +#: model:ir.model.fields,field_description:pms.field_res_partner__residence_zip +#: model:ir.model.fields,field_description:pms.field_res_users__residence_zip +msgid "Zip of residence" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__residence_zip +#: model:ir.model.fields,help:pms.field_pms_property__residence_zip +#: model:ir.model.fields,help:pms.field_res_partner__residence_zip +#: model:ir.model.fields,help:pms.field_res_users__residence_zip +msgid "Zip of the guest's residence" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_room__is_shared_room +msgid "allows you to reserve units smaller than the room itself (eg beds)" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__free_room_ids +msgid "" +"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" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_property__availability +msgid "" +"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" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +msgid "" +"availability rules\n" +" will be overwritten:" +msgstr "" + +#. module: pms +#: code:addons/pms/models/pms_reservation.py:0 +#, python-format +msgid "booking agency with wrong configuration: " +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation_split_join_swap_wizard__checkout +msgid "checkout in reservation" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_property__residence_city +#: model:ir.model.fields,field_description:pms.field_res_partner__residence_city +#: model:ir.model.fields,field_description:pms.field_res_users__residence_city +msgid "city of residence" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_folio_error +#: model_terms:ir.ui.view,arch_db:pms.portal_folio_success +msgid "close" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.portal_my_prechekin_folio +msgid "completed" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "email" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__birthdate_date +msgid "host birthdate" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__firstname +msgid "host firstname" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__gender +msgid "host gender" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__lastname +msgid "host lastname" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__nationality_id +msgid "host nationality" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__lastname2 +msgid "host second lastname" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_checkin_partner__partner_incongruences +msgid "" +"indicates that some partner fields on the checkin do not " +"correspond to that of the associated partner" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_folio__partner_incongruences +msgid "" +"indicates that some partner fields on the folio do not " +"correspond to that of the associated partner" +msgstr "" + +#. module: pms +#: model:ir.model.fields,help:pms.field_pms_reservation__partner_incongruences +msgid "" +"indicates that some partner fields on the reservation do not " +"correspond to that of the associated partner" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +#: model_terms:ir.ui.view,arch_db:pms.pms_reservation_view_form +msgid "mobile" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +#: model_terms:ir.ui.view,arch_db:pms.reservation_wizard +msgid "or" +msgstr "" + +#. module: pms +#: model:ir.model.fields,field_description:pms.field_pms_checkin_partner__partner_incongruences +#: model:ir.model.fields,field_description:pms.field_pms_folio__partner_incongruences +#: model:ir.model.fields,field_description:pms.field_pms_reservation__partner_incongruences +msgid "partner_incongruences" +msgstr "" + +#. module: pms +#: model:ir.actions.act_window,name:pms.action_pms_room_form +msgid "pms Room" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.massive_changes_wizard +msgid "" +"pricelist items\n" +" will be overwritten:" +msgstr "" + +#. module: pms +#: model_terms:ir.ui.view,arch_db:pms.pms_folio_view_form +msgid "" +"these are the billing information associated with the\n" +" booking client or the company (if a company is\n" +" assigned). If you want to bill an independent contact,\n" +" you can select it in the billing assistant" +msgstr "" diff --git a/pms/init_hook.py b/pms/init_hook.py new file mode 100644 index 0000000000..5592fe58f7 --- /dev/null +++ b/pms/init_hook.py @@ -0,0 +1,16 @@ +from odoo import SUPERUSER_ID +from odoo.api import Environment + + +def pre_init_hook(cr): + with Environment.manage(): + env = Environment(cr, SUPERUSER_ID, {}) + ResConfig = env["res.config.settings"] + default_values = ResConfig.default_get(list(ResConfig.fields_get())) + default_values.update( + {"group_product_pricelist": True, "group_sale_pricelist": True} + ) + ResConfig.sudo().create(default_values).execute() + env["ir.config_parameter"].sudo().set_param( + "product.product_pricelist_setting", "advanced" + ) diff --git a/pms/migrations/14.0.2.22.1/post-migration.py b/pms/migrations/14.0.2.22.1/post-migration.py new file mode 100644 index 0000000000..73a658c1e3 --- /dev/null +++ b/pms/migrations/14.0.2.22.1/post-migration.py @@ -0,0 +1,2 @@ +def migrate(cr, version): + cr.execute("UPDATE pms_reservation SET to_send_mail = NOT is_mail_send") diff --git a/pms/migrations/14.0.2.35.1/post-migration.py b/pms/migrations/14.0.2.35.1/post-migration.py new file mode 100644 index 0000000000..16bff610c7 --- /dev/null +++ b/pms/migrations/14.0.2.35.1/post-migration.py @@ -0,0 +1,15 @@ +from openupgradelib import openupgrade + + +@openupgrade.migrate() +def migrate(env, version): + openupgrade.logged_query( + env.cr, + """ + UPDATE pms_reservation + SET to_send_confirmation_mail = False, + to_send_cancelation_mail = False, + to_send_exit_mail = False, + to_send_modification_mail = False; + """, + ) diff --git a/pms/migrations/14.0.2.36.2/post-migration.py b/pms/migrations/14.0.2.36.2/post-migration.py new file mode 100644 index 0000000000..e5a4272836 --- /dev/null +++ b/pms/migrations/14.0.2.36.2/post-migration.py @@ -0,0 +1,13 @@ +import logging + +from openupgradelib import openupgrade + +_logger = logging.getLogger(__name__) + + +@openupgrade.migrate() +def migrate(env, version): + _logger.info("Recompute reservations sale channel ids...") + env["pms.reservation"].search( + [("reservation_type", "!=", "out")] + )._compute_sale_channel_ids() diff --git a/pms/migrations/14.0.2.36.2/pre-migration.py b/pms/migrations/14.0.2.36.2/pre-migration.py new file mode 100644 index 0000000000..2a8bbe652f --- /dev/null +++ b/pms/migrations/14.0.2.36.2/pre-migration.py @@ -0,0 +1,53 @@ +import logging + +from openupgradelib import openupgrade + +_logger = logging.getLogger(__name__) + +_field_renames = [ + ("pms.folio", "pms_folio", "channel_type_id", "sale_channel_origin_id"), + ("pms.reservation", "pms_reservation", "channel_type_id", "sale_channel_origin_id"), +] +_field_creates = [ + ( + "sale_channel_id", + "pms.reservation.line", + "pms_reservation_line", + "many2one", + "integer", + "pms", + ), + ( + "sale_channel_origin_id", + "pms.service", + "pms_service", + "many2one", + "integer", + "pms", + ), +] + + +@openupgrade.migrate() +def migrate(env, version): + openupgrade.rename_fields(env, _field_renames) + openupgrade.add_fields(env, _field_creates) + openupgrade.logged_query( + env.cr, + """ + UPDATE pms_reservation_line rl + SET sale_channel_id = r.sale_channel_origin_id + FROM pms_reservation r + WHERE r.id = rl.reservation_id + """, + ) + + openupgrade.logged_query( + env.cr, + """ + UPDATE pms_service ser + SET sale_channel_origin_id = fol.sale_channel_origin_id + FROM pms_folio fol + WHERE fol.id = ser.folio_id + """, + ) diff --git a/pms/models/__init__.py b/pms/models/__init__.py new file mode 100644 index 0000000000..398c5af2dd --- /dev/null +++ b/pms/models/__init__.py @@ -0,0 +1,55 @@ +# Copyright 2018 Alexandre Díaz +# Copyright 2018 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import ir_http +from . import ir_config_parameter + +from . import pms_board_service_room_type +from . import pms_property +from . import res_users +from . import pms_ubication +from . import pms_folio +from . import pms_reservation +from . import pms_room +from . import pms_amenity +from . import pms_amenity_type +from . import pms_room_type +from . import pms_service +from . import account_move +from . import product_template +from . import product_product +from . import res_company +from . import account_payment +from . import pms_availability_plan +from . import pms_availability_plan_rule +from . import pms_reservation_line +from . import pms_checkin_partner +from . import product_pricelist +from . import product_pricelist_item +from . import res_partner +from . import pms_sale_channel +from . import mail_compose_message +from . import pms_room_type_class +from . import pms_room_closure_reason +from . import pms_service_line +from . import pms_board_service +from . import pms_board_service_room_type_line +from . import pms_board_service_line +from . import account_move_line +from . import pms_cancelation_rule +from . import folio_sale_line +from . import account_bank_statement_line +from . import account_bank_statement +from . import account_journal +from . import pms_availability +from . import res_partner_id_number +from . import pms_automated_mails +from . import payment_transaction +from . import pms_team_member +from . import ir_pms_property +from . import payment_acquirer +from . import account_analytic_line +from . import res_partner_category +from . import res_country +from . import res_partner_id_category diff --git a/pms/models/account_analytic_line.py b/pms/models/account_analytic_line.py new file mode 100644 index 0000000000..4524019bc7 --- /dev/null +++ b/pms/models/account_analytic_line.py @@ -0,0 +1,34 @@ +from odoo import fields, models + + +class AccountAnalyticLine(models.Model): + _inherit = "account.analytic.line" + _check_pms_properties_auto = True + + pms_property_id = fields.Many2one( + name="Property", + comodel_name="pms.property", + compute="_compute_pms_property_id", + store=True, + readonly=False, + check_pms_properties=True, + index=True, + ) + + def _compute_pms_property_id(self): + for rec in self: + if rec.move_id and rec.move_id.pms_property_id: + rec.pms_property_id = rec.move_id.pms_property_id + elif not rec.pms_property_id: + rec.pms_property_id = False + + +class AccountAnalyticDistribution(models.Model): + _inherit = "account.analytic.distribution.model" + + pms_property_id = fields.Many2one( + name="Property", + comodel_name="pms.property", + check_pms_properties=True, + index=True, + ) diff --git a/pms/models/account_bank_statement.py b/pms/models/account_bank_statement.py new file mode 100644 index 0000000000..a58aed2cf5 --- /dev/null +++ b/pms/models/account_bank_statement.py @@ -0,0 +1,46 @@ +from odoo import api, fields, models + + +class AccountBankStatement(models.Model): + _inherit = "account.bank.statement" + _check_pms_properties_auto = True + + pms_property_id = fields.Many2one( + string="Property", + help="Properties with access to the element", + comodel_name="pms.property", + readonly=False, + compute="_compute_pms_property_id", + store=True, + copy=False, + index=True, + check_pms_properties=True, + ) + journal_id = fields.Many2one(check_pms_properties=True) + + @api.depends("journal_id") + def _compute_pms_property_id(self): + for record in self: + if len(record.journal_id.pms_property_ids) == 1: + record.pms_property_id = record.journal_id.pms_property_ids[0] + elif not record.pms_property_id: + record.pms_property_id = False + + def button_post(self): + """ + Override the default method to add autoreconcile payments and statement lines + """ + lines_of_moves_to_post = self.line_ids.filtered( + lambda line: line.move_id.state != "posted" + ) + super(AccountBankStatement, self).button_post() + for line in lines_of_moves_to_post: + payment_move_line = line._get_payment_move_lines_to_reconcile() + statement_move_line = line.move_id.line_ids.filtered( + lambda line: line.account_id.reconcile + or line.account_id == line.journal_id.suspense_account_id + ) + if payment_move_line and statement_move_line: + statement_move_line.account_id = payment_move_line.account_id + lines_to_reconcile = payment_move_line + statement_move_line + lines_to_reconcile.reconcile() diff --git a/pms/models/account_bank_statement_line.py b/pms/models/account_bank_statement_line.py new file mode 100644 index 0000000000..576fe52d54 --- /dev/null +++ b/pms/models/account_bank_statement_line.py @@ -0,0 +1,94 @@ +from odoo import api, fields, models + + +class AccountBankStatementLine(models.Model): + _inherit = "account.bank.statement.line" + _check_pms_properties_auto = True + + folio_ids = fields.Many2many( + string="Folios", + comodel_name="pms.folio", + ondelete="cascade", + relation="account_bank_statement_folio_rel", + column1="account_journal_id", + column2="folio_id", + check_pms_properties=True, + ) + reservation_ids = fields.Many2many( + string="Reservations", + help="Reservations in which the Account Bank Statement Lines are included", + comodel_name="pms.reservation", + ondelete="cascade", + relation="account_bank_statement_reservation_rel", + column1="account_bank_statement_id", + column2="reservation_id", + check_pms_properties=True, + ) + service_ids = fields.Many2many( + string="Services", + help="Services in which the Account Bank Statement Lines are included", + comodel_name="pms.service", + ondelete="cascade", + relation="account_bank_statement_service_rel", + column1="account_bank_statement_id", + column2="service_id", + check_pms_properties=True, + ) + + @api.model + def _prepare_move_line_default_vals(self, counterpart_account_id=None): + line_vals_list = super( + AccountBankStatementLine, self + )._prepare_move_line_default_vals(counterpart_account_id) + if self.folio_ids: + for line in line_vals_list: + line.update( + { + "folio_ids": [(6, 0, self.folio_ids.ids)], + } + ) + return line_vals_list + + def _get_payment_move_lines_to_reconcile(self): + self.ensure_one() + payment_move_line = False + folio_ids = self.folio_ids and self.folio_ids.ids or False + domain = [("move_id.folio_ids", "in", folio_ids)] if folio_ids else [] + domain.extend( + [ + ("move_id.ref", "=", self.payment_ref), + ("date", "=", self.date), + ("reconciled", "=", False), + "|", + ( + "account_id", + "=", + self.journal_id.payment_debit_account_id.id, + ), + ( + "account_id", + "=", + self.journal_id.payment_credit_account_id.id, + ), + ("journal_id", "=", self.journal_id.id), + ] + ) + to_reconcile_move_lines = self.env["account.move.line"].search(domain) + # We try to reconcile by amount + for record in to_reconcile_move_lines: + payment_move_line = record if record.balance == self.amount else False + return payment_move_line + + def _create_counterpart_and_new_aml( + self, counterpart_moves, counterpart_aml_dicts, new_aml_dicts + ): + for aml_dict in new_aml_dicts: + if aml_dict.get("pms_property_id"): + self.move_id.pms_property_id = False + break + return super( + AccountBankStatementLine, + self.with_context(no_recompute_move_pms_property=True), + )._create_counterpart_and_new_aml( + counterpart_moves, counterpart_aml_dicts, new_aml_dicts + ) diff --git a/pms/models/account_journal.py b/pms/models/account_journal.py new file mode 100644 index 0000000000..82b51e21a8 --- /dev/null +++ b/pms/models/account_journal.py @@ -0,0 +1,75 @@ +from odoo import _, api, fields, models + + +class AccountJournal(models.Model): + _inherit = "account.journal" + _check_pms_properties_auto = True + + pms_property_ids = fields.Many2many( + string="Properties", + help="Properties with access to the element;" + " if not set, all properties can access", + comodel_name="pms.property", + ondelete="restrict", + relation="account_journal_pms_property_rel", + column1="account_journal_id", + column2="pms_property_id", + check_pms_properties=True, + ) + allowed_pms_payments = fields.Boolean( + string="For manual payments", + help="Use to pay for reservations", + ) + avoid_autoinvoice_downpayment = fields.Boolean( + string="Avoid autoinvoice downpayment", + help="Avoid autoinvoice downpayment", + default=False, + ) + is_simplified_invoice = fields.Boolean( + string="Simplified invoice", + help="Use to simplified invoice", + compute="_compute_is_simplified_invoice", + readonly=False, + store=True, + ) + + @api.depends("pms_property_ids", "pms_property_ids.journal_simplified_invoice_id") + def _compute_is_simplified_invoice(self): + self.is_simplified_invoice = False + for journal in self: + if journal.id in journal.pms_property_ids.mapped( + "journal_simplified_invoice_id.id" + ): + journal.is_simplified_invoice = True + + @api.constrains("is_simplified_invoice") + def _check_pms_properties_simplified_invoice(self): + for journal in self: + if ( + journal.is_simplified_invoice + and journal.id + in journal.pms_property_ids.mapped("journal_normal_invoice_id.id") + ): + raise models.ValidationError( + _( + "The journal %s is used for normal invoices in the properties: %s" + % ( + journal.name, + ", ".join(journal.pms_property_ids.mapped("name")), + ) + ) + ) + if ( + not journal.is_simplified_invoice + and journal.id + in journal.pms_property_ids.mapped("journal_simplified_invoice_id.id") + ): + raise models.ValidationError( + _( + "The journal %s is used for simplified invoices in the properties: %s" + % ( + journal.name, + ", ".join(journal.pms_property_ids.mapped("name")), + ) + ) + ) diff --git a/pms/models/account_move.py b/pms/models/account_move.py new file mode 100644 index 0000000000..b3d0b06705 --- /dev/null +++ b/pms/models/account_move.py @@ -0,0 +1,396 @@ +# Copyright 2017 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) +import itertools as it + +from odoo import _, api, fields, models +from odoo.exceptions import UserError, ValidationError + + +class AccountMove(models.Model): + _inherit = "account.move" + _check_pms_properties_auto = True + + folio_ids = fields.Many2many( + string="Folios", + help="Folios where the account move are included", + comodel_name="pms.folio", + compute="_compute_folio_origin", + relation="account_move_folio_ids_rel", + column1="account_move_id", + column2="folio_ids_id", + store=True, + readonly=False, + ) + pms_property_id = fields.Many2one( + string="Property", + help="The property associated to the account move", + comodel_name="pms.property", + compute="_compute_pms_property_id", + store=True, + readonly=False, + index=True, + # check_pms_properties=True, + ) + # journal_id = fields.Many2one(check_pms_properties=True) + is_simplified_invoice = fields.Boolean( + help="Technical field to know if the invoice is simplified", + related="journal_id.is_simplified_invoice", + store=True, + ) + origin_agency_id = fields.Many2one( + string="Origin Agency", + help="The agency where the folio account move originates", + comodel_name="res.partner", + domain="[('is_agency', '=', True)]", + compute="_compute_origin_agency_id", + store=True, + index=True, + readonly=False, + ) + + @api.onchange("pms_property_id") + def _onchange_pms_property_id(self): + for move in self: + journals = self.env["account.journal"].search( + [ + ("pms_property_ids", "=", move.pms_property_id.id), + ] + ) + if journals: + move.journal_id = journals[0] + else: + move.journal_id = False + + @api.depends("journal_id", "folio_ids") + def _compute_pms_property_id(self): + for move in self: + if self.env.context.get("force_pms_property"): + move.pms_property_id = self.env.context["force_pms_property"] + elif move.folio_ids and len(move.folio_ids.mapped("pms_property_id")) == 1: + move.pms_property_id = move.folio_ids.mapped("pms_property_id") + elif len( + move.journal_id.mapped("pms_property_ids") + ) == 1 and not self.env.context.get("no_recompute_move_pms_property"): + move.pms_property_id = move.journal_id.mapped("pms_property_ids")[0] + elif not move.journal_id.pms_property_ids: + move.pms_property_id = False + elif not move.pms_property_id: + move.pms_property_id = False + + @api.depends("line_ids", "line_ids.folio_ids") + def _compute_folio_origin(self): + for move in self: + move.folio_ids = False + move.folio_ids = move.mapped("line_ids.folio_ids.id") + + @api.depends("line_ids", "line_ids.origin_agency_id") + def _compute_origin_agency_id(self): + """ + Compute the origin agency of the account move + if the move has multiple agencies in origin, + the first one is returned (REVIEW: is this correct?) + """ + self.origin_agency_id = False + for move in self: + agencies = move.mapped("line_ids.origin_agency_id") + if agencies: + move.origin_agency_id = agencies[0] + + # def _compute_payments_widget_to_reconcile_info(self): + # for move in self: + # if not move.line_ids.folio_line_ids: + # super(AccountMove, move)._compute_payments_widget_to_reconcile_info() + # else: + # move.invoice_outstanding_credits_debits_widget = json.dumps(False) + # move.invoice_has_outstanding = False + + # if ( + # move.state != "posted" + # or move.payment_state not in ("not_paid", "partial") + # or not move.is_invoice(include_receipts=True) + # ): + # continue + + # pay_term_lines = move.line_ids.filtered( + # lambda line: line.account_id.account_type + # in ("asset_receivable", "payable") + # ) + + # payments_widget_vals = { + # "outstanding": True, + # "content": [], + # "move_id": move.id, + # } + + # if move.is_inbound(): + # domain = [("balance", "<", 0.0)] + # payments_widget_vals["title"] = _("Outstanding credits") + # else: + # domain = [("balance", ">", 0.0)] + # payments_widget_vals["title"] = _("Outstanding debits") + + # domain.extend( + # [ + # ("account_id", "in", pay_term_lines.account_id.ids), + # ("parent_state", "=", "posted"), + # ("reconciled", "=", False), + # "|", + # ("amount_residual", "!=", 0.0), + # ("amount_residual_currency", "!=", 0.0), + # "|", + # ( + # "folio_ids", + # "in", + # move.line_ids.mapped("folio_line_ids.folio_id.id"), + # ), + # ("partner_id", "=", move.commercial_partner_id.id), + # ] + # ) + + # for line in self.env["account.move.line"].search(domain): + # if line.currency_id == move.currency_id: + # # Same foreign currency. + # amount = abs(line.amount_residual_currency) + # else: + # # Different foreign currencies. + # amount = move.company_currency_id._convert( + # abs(line.amount_residual), + # move.currency_id, + # move.company_id, + # line.date, + # ) + + # if move.currency_id.is_zero(amount): + # continue + + # payments_widget_vals["content"].append( + # { + # "journal_name": line.ref or line.move_id.name, + # "amount": amount, + # "currency": move.currency_id.symbol, + # "id": line.id, + # "move_id": line.move_id.id, + # "position": move.currency_id.position, + # "digits": [69, move.currency_id.decimal_places], + # "payment_date": fields.Date.to_string(line.date), + # } + # ) + + # if not payments_widget_vals["content"]: + # continue + + # move.invoice_outstanding_credits_debits_widget = json.dumps( + # payments_widget_vals + # ) + # move.invoice_has_outstanding = True + + def _search_default_journal(self): + """ + Search for the default journal based on the journal type and property, + the parent method is overwritten to add the property filter if + default_pms_property_id is set in context + """ + journal = super(AccountMove, self)._search_default_journal() + company_id = self._context.get("default_company_id", self.env.company.id) + company = self.env["res.company"].browse(company_id) + pms_property_id = self.env.context.get( + "default_pms_property_id", self.pms_property_id.id + ) or ( + self.env.user.get_active_property_ids() + and self.env.user.get_active_property_ids()[0] + ) + pms_property = self.env["pms.property"].browse(pms_property_id) + if pms_property: + domain = [ + ("company_id", "=", pms_property.company_id.id), + ("pms_property_ids", "in", pms_property.id), + ] + journal = self.env["account.journal"].search(domain, limit=1) + if not journal: + domain = [ + ("company_id", "=", pms_property.company_id.id), + ("pms_property_ids", "=", False), + ] + journal = self.env["account.journal"].search(domain, limit=1) + else: + domain = [ + ("company_id", "=", company_id), + ("pms_property_ids", "=", False), + ] + journal = self.env["account.journal"].search(domain, limit=1) + if not journal: + if pms_property: + error_msg = _( + "No journal could be found in property %(property_name)s", + property_name=pms_property.display_name, + ) + else: + error_msg = _( + "No journal could be found in company %(company_name)s", + company_name=company.display_name, + ) + raise UserError(error_msg) + return journal + + @api.depends("pms_property_id") + def _compute_suitable_journal_ids(self): + super(AccountMove, self)._compute_suitable_journal_ids() + for move in self: + if move.pms_property_id: + move.suitable_journal_ids = move.suitable_journal_ids.filtered( + lambda j: not j.pms_property_ids + or move.pms_property_id.id in j.pms_property_ids.ids + ) + + # def _autoreconcile_folio_payments(self): + # """ + # Reconcile payments with the invoice + # """ + # # TODO: Add setting option to enable automatic payment reconciliation + # for move in self.filtered(lambda m: m.state == "posted"): + # if move.is_invoice(include_receipts=True) and move.folio_ids: + # to_reconcile_payments_widget_vals = json.loads( + # move.invoice_outstanding_credits_debits_widget + # ) + # if not to_reconcile_payments_widget_vals: + # continue + # current_amounts = { + # vals["move_id"]: vals["amount"] + # for vals in to_reconcile_payments_widget_vals["content"] + # } + # pay_term_lines = move.line_ids.filtered( + # lambda line: line.account_id.user_type_id.type + # in ("receivable", "payable") + # ) + # to_propose = ( + # self.env["account.move"] + # .browse(list(current_amounts.keys())) + # .line_ids.filtered( + # lambda line: line.account_id == pay_term_lines.account_id + # and line.folio_ids in move.folio_ids + # ) + # ) + # to_reconcile = self.match_pays_by_amount( + # payments=to_propose, invoice=move + # ) + # if to_reconcile: + # (pay_term_lines + to_reconcile).reconcile() + + # return True + + def _post(self, soft=True): + """ + Overwrite the original method to add the folio_ids to the invoice + """ + for record in self: + record._check_pms_valid_invoice(record) + res = super(AccountMove, self)._post(soft) + # self._autoreconcile_folio_payments() + return res + + def match_pays_by_amount(self, payments, invoice): + """ + Match payments by amount + """ + for i in range(len(payments)): + combinations = list(it.combinations(payments, i + 1)) + for combi in combinations: + # TODO: compare with currency differences + if sum(abs(item.balance) for item in combi) == invoice.amount_residual: + return payments.filtered( + lambda p: p.id in [item.id for item in combi] + ) + if sum(invoice.folio_ids.mapped("pending_amount")) == 0: + return payments + return [] + + @api.model + def _check_pms_valid_invoice(self, move): + """ + Check invoice and receipts legal status + """ + if ( + move.company_id.check_min_partner_data_invoice + and move.is_invoice(include_receipts=True) + and not move.journal_id.is_simplified_invoice + and ( + not move.partner_id or not move.partner_id._check_enought_invoice_data() + ) + ): + raise UserError( + _( + "You cannot validate this invoice. Please check the " + " partner has the complete information required." + ) + ) + if move.journal_id.is_simplified_invoice: + move._check_simplified_restrictions() + return True + + def _check_simplified_restrictions(self): + self.ensure_one() + if ( + self.pms_property_id + and self.amount_total > self.pms_property_id.max_amount_simplified_invoice + and ( + not self.pms_property_id.avoid_simplified_max_amount_downpayment + or not self._is_downpayment() + ) + ): + mens = _( + "The total amount of the simplified invoice is higher than the " + "maximum amount allowed for simplified invoices." + ) + if self.folio_ids: + self.folio_ids.message_post(body=mens) + raise ValidationError(mens) + return True + + def _proforma_access_url(self): + self.ensure_one() + if self.is_invoice(include_receipts=True): + return "/my/invoices/proforma/%s" % (self.id) + else: + return False + + def get_proforma_portal_url( + self, + suffix=None, + report_type=None, + download=None, + query_string=None, + anchor=None, + ): + """ + Get a proforma portal url for this model, including access_token. + The associated route must handle the flags for them to have any effect. + - suffix: string to append to the url, before the query string + - report_type: report_type query string, often one of: html, pdf, text + - download: set the download query string to true + - query_string: additional query string + - anchor: string to append after the anchor # + """ + self.ensure_one() + url = self._proforma_access_url() + "%s?access_token=%s%s%s%s%s" % ( + suffix if suffix else "", + self._portal_ensure_token(), + "&report_type=%s" % report_type if report_type else "", + "&download=true" if download else "", + query_string if query_string else "", + "#%s" % anchor if anchor else "", + ) + return url + + def _is_downpayment(self): + self.ensure_one() + if self.folio_ids: + return ( + self.line_ids.folio_line_ids + and all( + folio_line.is_downpayment + for folio_line in self.line_ids.folio_line_ids + ) + or False + ) + else: + return super(AccountMove, self)._is_downpayment() diff --git a/pms/models/account_move_line.py b/pms/models/account_move_line.py new file mode 100644 index 0000000000..3b9f287a35 --- /dev/null +++ b/pms/models/account_move_line.py @@ -0,0 +1,177 @@ +# Copyright 2017 Alexandre Díaz +# Copyright 2017 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) +from odoo import _, api, fields, models + + +class AccountMoveLine(models.Model): + _inherit = "account.move.line" + _check_pms_properties_auto = True + + # Fields declaration + # TODO: REVIEW why not a Many2one? + name = fields.Char( + compute="_compute_name", + store=True, + readonly=False, + ) + folio_line_ids = fields.Many2many( + string="Folio Lines", + help="The folio lines in the account move lines", + copy=True, + comodel_name="folio.sale.line", + relation="folio_sale_line_invoice_rel", + column1="invoice_line_id", + column2="sale_line_id", + ) + folio_ids = fields.Many2many( + comodel_name="pms.folio", + string="Folios", + compute="_compute_folio_ids", + store=True, + check_pms_properties=True, + ) + name_changed_by_user = fields.Boolean( + string="Name set manually", + help="""Techinal field to know if the name was set manually by the user + or by the system. If the name was set manually, the system will not + change it when the qty days are changed""", + default=True, + ) + pms_property_id = fields.Many2one( + name="Property", + comodel_name="pms.property", + compute="_compute_pms_property_id", + store=True, + readonly=False, + index=True, + check_pms_properties=True, + ) + origin_agency_id = fields.Many2one( + string="Origin Agency", + help="The agency where the folio account move originates", + comodel_name="res.partner", + domain="[('is_agency', '=', True)]", + compute="_compute_origin_agency_id", + store=True, + index=True, + readonly=False, + ) + move_id = fields.Many2one(check_pms_properties=True) + + @api.depends("move_id.payment_reference", "quantity") + def _compute_name(self): + res = super()._compute_name() + for record in self: + if record.folio_line_ids and not record.name: + 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, + ) + return res + + @api.depends("move_id") + def _compute_pms_property_id(self): + for rec in self: + if rec.move_id and rec.move_id.pms_property_id: + rec.pms_property_id = rec.move_id.pms_property_id + elif not rec.pms_property_id: + rec.pms_property_id = False + + @api.depends( + "folio_line_ids", + "payment_id", + "payment_id.folio_ids", + "statement_line_id", + "statement_line_id.folio_ids", + ) + def _compute_folio_ids(self): + if self.folio_line_ids: + self.folio_ids = self.folio_line_ids.mapped("folio_id") + elif self.payment_id: + self.folio_ids = self.payment_id.folio_ids + elif self.statement_line_id: + self.folio_ids = self.statement_line_id.folio_ids + else: + self.folio_ids = False + + @api.depends("folio_line_ids") + def _compute_origin_agency_id(self): + """ + Compute the origin agency of the account move line, + if the line has multiple agencies in origin, + (p.e. nights with different agencies in origin), + the first one is returned (REVIEW: is this correct?) + """ + self.origin_agency_id = False + for line in self: + agencies = line.mapped("folio_line_ids.origin_agency_id") + if agencies: + line.origin_agency_id = agencies[0] + + def _prepare_analytic_distribution_line(self, distribution): + vals = super()._prepare_analytic_distribution_line(distribution) + if distribution.pms_property_id: + vals["pms_property_id"] = distribution.pms_property_id.id + return vals + + def _prepare_analytic_line(self): + result = super()._prepare_analytic_line() + for move_line in result: + move = self.browse(move_line["move_id"]) + if move.pms_property_id or move.move_id.pms_property_id: + move_line["pms_property_id"] = ( + move.pms_property_id.id or move.move_id.pms_property_id.id + ) + return result + + def reconcile(self): + """ + Reconcile the account move + """ + res = super(AccountMoveLine, self).reconcile() + # Update partner in payments and statement lines + for record in self: + if record.payment_id: + old_payment_partner = record.payment_id.partner_id + new_payment_partner = record.payment_id.mapped( + "reconciled_invoice_ids.partner_id" + ) + if ( + old_payment_partner != new_payment_partner + and len(new_payment_partner) == 1 + ): + record.payment_id.partner_id = new_payment_partner + if old_payment_partner: + record.payment_id.message_post( + body=_( + f""" + Partner modify automatically from invoice: + {old_payment_partner.name} to {new_payment_partner.name} + """ + ) + ) + if record.statement_line_id: + old_statement_partner = record.statement_line_id.partner_id + new_payment_partner = record.payment_id.mapped( + "reconciled_invoice_ids.partner_id" + ) + if ( + old_statement_partner != new_payment_partner + and len(new_payment_partner) == 1 + ): + record.statement_line_id.partner_id = new_payment_partner + if old_statement_partner: + record.statement_line_id.message_post( + body=_( + f""" + Partner modify automatically from invoice: + {old_statement_partner.name} to {new_payment_partner.name} + """ + ) + ) + return res diff --git a/pms/models/account_payment.py b/pms/models/account_payment.py new file mode 100644 index 0000000000..f092620da9 --- /dev/null +++ b/pms/models/account_payment.py @@ -0,0 +1,218 @@ +# Copyright 2017 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from collections import defaultdict + +from dateutil.relativedelta import relativedelta + +from odoo import _, api, fields, models + + +class AccountPayment(models.Model): + _inherit = "account.payment" + + folio_ids = fields.Many2many( + string="Folios", + comodel_name="pms.folio", + compute="_compute_folio_ids", + store=True, + readonly=False, + relation="account_payment_folio_rel", + column1="payment_id", + column2="folio_id", + ) + origin_agency_id = fields.Many2one( + string="Origin Agency", + help="The agency where the folio account move originates", + comodel_name="res.partner", + domain="[('is_agency', '=', True)]", + compute="_compute_origin_agency_id", + store=True, + index=True, + readonly=True, + ) + origin_reference = fields.Char( + string="Origin Reference", + help="The reference of the payment origin", + ) + + @api.depends("reconciled_invoice_ids", "reconciled_bill_ids") + def _compute_origin_agency_id(self): + """ + Compute the origin agency of the sale line, + if the line has multiple agencies in origin, + (p.e. nights with different agencies in origin), + the first one is returned (REVIEW: is this correct?) + """ + for rec in self: + inv_agency_ids = rec.reconciled_invoice_ids.mapped( + "line_ids.folio_line_ids.origin_agency_id.id" + ) + bill_agency_ids = rec.reconciled_bill_ids.mapped( + "line_ids.folio_line_ids.origin_agency_id.id" + ) + agency_ids = list(set(inv_agency_ids + bill_agency_ids)) + if agency_ids: + rec.write({"origin_agency_id": agency_ids[0]}) + elif ( + not rec.reconciled_invoice_ids + and not rec.reconciled_bill_ids + and rec.folio_ids + ): + rec.origin_agency_id = rec.origin_agency_id + else: + rec.origin_agency_id = False + + @api.depends("reconciled_invoice_ids", "reconciled_bill_ids") + def _compute_folio_ids(self): + for rec in self: + inv_folio_ids = rec.reconciled_invoice_ids.mapped( + "line_ids.folio_line_ids.folio_id.id" + ) + bill_folio_ids = rec.reconciled_bill_ids.mapped( + "line_ids.folio_line_ids.folio_id.id" + ) + folio_ids = list(set(inv_folio_ids + bill_folio_ids)) + # If the payment was already assigned to a specific page of the invoice, + # we do not want it to be associated with others + if folio_ids and len(set(rec.folio_ids.ids) & set(folio_ids)) == 0: + folios = self.env["pms.folio"].browse(folio_ids) + # If the payment is in a new invoice, we want it to be associated with all + # folios of the invoice that don't are paid yet + folio_ids = folios.filtered(lambda f: f.pending_amount > 0).ids + rec.write({"folio_ids": [(6, 0, folio_ids)]}) + elif not rec.folio_ids: + rec.folio_ids = False + + def _prepare_move_line_default_vals(self, write_off_line_vals=None): + line_vals_list = super(AccountPayment, self)._prepare_move_line_default_vals( + write_off_line_vals + ) + if self.folio_ids: + for line in line_vals_list: + line.update( + { + "folio_ids": [(6, 0, self.folio_ids.ids)], + } + ) + return line_vals_list + + def _synchronize_to_moves(self, changed_fields): + super(AccountPayment, self)._synchronize_to_moves(changed_fields) + if "folio_ids" in changed_fields: + for pay in self.with_context(skip_account_move_synchronization=True): + pay.move_id.write( + { + "folio_ids": [(6, 0, pay.folio_ids.ids)], + } + ) + + def action_draft(self): + for payment in self: + if payment._check_has_downpayment_invoice(payment): + downpayment_invoices = payment.reconciled_invoice_ids.filtered( + lambda inv: inv._is_downpayment() + ) + if downpayment_invoices.state == "posted": + default_values_list = [ + { + "ref": _(f'Reversal of: {move.name + " - " + move.ref}'), + } + for move in downpayment_invoices + ] + downpayment_invoices._reverse_moves( + default_values_list, cancel=True + ) + else: + downpayment_invoices.unlink() + return super(AccountPayment, self).action_draft() + + @api.model + def auto_invoice_downpayments(self, offset=0): + """ + This method is called by a cron job to invoice the downpayments + based on the company settings. + """ + date_reference = fields.Date.today() - relativedelta(days=offset) + payments = self._get_downpayments_to_invoice(date_reference) + for payment in payments: + partner_id = ( + payment.partner_id.id or self.env.ref("pms.various_pms_partner").id + ) + self._create_downpayment_invoice( + payment=payment, + partner_id=partner_id, + ) + return True + + @api.model + def _get_downpayments_to_invoice(self, date_reference): + companys = self.env["res.company"].search([]) + payments = self.env["account.payment"] + for company in companys: + if company.pms_invoice_downpayment_policy == "all": + date_ref = fields.Date.today() + elif company.pms_invoice_downpayment_policy == "checkout_past_month": + date_ref = fields.Date.today().replace( + day=1, month=fields.Date.today().month + 1 + ) + else: + continue + payments += self.search( + [ + ("state", "=", "posted"), + ("partner_type", "=", "customer"), + ("company_id", "=", company.id), + ("journal_id.avoid_autoinvoice_downpayment", "=", False), + ("folio_ids", "!=", False), + ("folio_ids.last_checkout", ">=", date_ref), + ("date", "<=", date_reference), + ] + ) + payments = payments.filtered(lambda p: not p.reconciled_invoice_ids) + return payments + + @api.model + def _check_has_downpayment_invoice(self, payment): + if ( + payment.folio_ids + and payment.partner_type == "customer" + and payment.reconciled_invoice_ids.filtered( + lambda inv: inv._is_downpayment() + ) + ): + return True + return False + + @api.model + def _create_downpayment_invoice(self, payment, partner_id): + invoice_wizard = self.env["folio.advance.payment.inv"].create( + { + "partner_invoice_id": partner_id, + "advance_payment_method": "fixed", + "fixed_amount": payment.amount, + } + ) + move = invoice_wizard.with_context( + active_ids=payment.folio_ids.ids, + return_invoices=True, + ).create_invoices() + if payment.payment_type == "outbound": + move.action_switch_invoice_into_refund_credit_note() + move.action_post() + for invoice, payment_move in zip(move, payment.move_id): + group = defaultdict(list) + for line in (invoice.line_ids + payment_move.line_ids).filtered( + lambda l: not l.reconciled + ): + group[(line.account_id, line.currency_id)].append(line.id) + for (account, _dummy), line_ids in group.items(): + if account.reconcile or account.internal_type == "liquidity": + self.env["account.move.line"].browse(line_ids).reconcile() + # Set folio sale lines default_invoice_to to partner downpayment invoice + for folio in payment.folio_ids: + for sale_line in folio.sale_line_ids.filtered( + lambda l: not l.default_invoice_to + ): + sale_line.default_invoice_to = move.partner_id.id + + return move diff --git a/pms/models/folio_sale_line.py b/pms/models/folio_sale_line.py new file mode 100644 index 0000000000..f62c6f1f49 --- /dev/null +++ b/pms/models/folio_sale_line.py @@ -0,0 +1,1198 @@ +# Copyright 2020 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from datetime import timedelta +from math import ceil + +import babel.dates +from dateutil import relativedelta + +from odoo import _, api, fields, models +from odoo.exceptions import UserError +from odoo.osv import expression +from odoo.tools import float_compare +from odoo.tools.misc import get_lang + + +class FolioSaleLine(models.Model): + _name = "folio.sale.line" + _description = "Folio Sale Line" + _order = ( + "folio_id, sequence, reservation_order desc, service_order, name, date_order" + ) + _check_company_auto = True + + folio_id = fields.Many2one( + string="Folio Reference", + help="Folio to which folio sale line belongs", + required=True, + index=True, + copy=False, + comodel_name="pms.folio", + ondelete="cascade", + ) + reservation_id = fields.Many2one( + string="Reservation Reference", + help="Reservation to which folio sale line belongs", + index=True, + copy=False, + comodel_name="pms.reservation", + ondelete="cascade", + ) + service_id = fields.Many2one( + string="Service Reference", + help="Sevice included in folio sale line", + index=True, + copy=False, + comodel_name="pms.service", + ondelete="cascade", + ) + pms_property_id = fields.Many2one( + string="Property", + help="Property with access to the element;", + readonly=True, + store=True, + comodel_name="pms.property", + related="folio_id.pms_property_id", + index=True, + check_pms_properties=True, + ) + is_board_service = fields.Boolean( + string="Board Service", + help="Indicates if the service included in " + "folio sale line is part of a board service", + store=True, + related="service_id.is_board_service", + ) + name = fields.Text( + string="Description", + help="Description of folio sale line", + readonly=False, + store=True, + compute="_compute_name", + ) + reservation_line_ids = fields.Many2many( + string="Nights", + help="Reservation lines associated with folio sale line," + " they corresponds with nights", + comodel_name="pms.reservation.line", + ) + service_line_ids = fields.Many2many( + string="Service Lines", + help="Subservices included in folio sale line service", + comodel_name="pms.service.line", + ) + sequence = fields.Integer(string="Sequence", help="", default=10) + + invoice_lines = fields.Many2many( + string="Invoice Lines", + copy=False, + help="Folio sale line invoice lines", + comodel_name="account.move.line", + relation="folio_sale_line_invoice_rel", + column1="sale_line_id", + column2="invoice_line_id", + ) + invoice_status = fields.Selection( + string="Invoice Status", + help="Invoice Status; it can be: invoiced, to invoice, no", + readonly=True, + store=True, + selection=[ + ("invoiced", "Fully Invoiced"), + ("to_invoice", "To Invoice"), + ("no", "Nothing to Invoice"), + ], + compute="_compute_invoice_status", + ) + price_unit = fields.Float( + string="Unit Price", + help="Unit Price of folio sale line", + digits="Product Price", + ) + + price_subtotal = fields.Monetary( + string="Subtotal", + help="Subtotal price without taxes", + readonly=True, + store=True, + compute="_compute_amount", + ) + price_tax = fields.Float( + string="Total Tax", + help="Total of taxes in a reservation", + readonly=True, + store=True, + compute="_compute_amount", + ) + price_total = fields.Monetary( + string="Total", + help="Total price with taxes", + readonly=True, + store=True, + compute="_compute_amount", + ) + price_reduce = fields.Float( + string="Price Reduce", + help="Reduced price amount, that is, total price with discounts applied", + readonly=True, + store=True, + digits="Product Price", + compute="_compute_get_price_reduce", + ) + tax_ids = fields.Many2many( + string="Taxes", + help="Taxes applied in the folio sale line", + store=True, + comodel_name="account.tax", + compute="_compute_tax_ids", + domain=["|", ("active", "=", False), ("active", "=", True)], + ) + price_reduce_taxinc = fields.Monetary( + string="Price Reduce Tax inc", + help="Price with discounts applied and taxes included", + readonly=True, + store=True, + compute="_compute_get_price_reduce_tax", + ) + price_reduce_taxexcl = fields.Monetary( + string="Price Reduce Tax excl", + help="Price with discounts applied without taxes", + readonly=True, + store=True, + compute="_compute_get_price_reduce_notax", + ) + + discount = fields.Float( + string="Discount (%)", + help="Discount of total price in folio sale line", + readonly=False, + store=True, + digits="Discount", + compute="_compute_discount", + ) + + product_id = fields.Many2one( + string="Product", + help="Product associated with folio sale line, " + "can be product associated with service " + "or product associated with" + "reservation's room type, in other case it's false", + store=True, + comodel_name="product.product", + domain="[('sale_ok', '=', True),\ + ('is_pms_available', '=', True),\ + '|', ('company_id', '=', False), \ + ('company_id', '=', company_id)]", + ondelete="restrict", + compute="_compute_product_id", + index=True, + check_company=True, + change_default=True, + ) + product_uom_qty = fields.Float( + string="Quantity", + help="", + readonly=False, + store=True, + digits="Product Unit of Measure", + compute="_compute_product_uom_qty", + ) + product_uom = fields.Many2one( + string="Unit of Measure", + help="", + comodel_name="uom.uom", + index=True, + domain="[('category_id', '=', product_uom_category_id)]", + ) + product_uom_category_id = fields.Many2one( + string="Unit of Measure Category", + help="", + readonly=True, + related="product_id.uom_id.category_id", + ) + product_uom_readonly = fields.Boolean( + string="", help="", compute="_compute_product_uom_readonly" + ) + + product_custom_attribute_value_ids = fields.One2many( + string="Custom Values", + copy=True, + comodel_name="product.attribute.custom.value", + inverse_name="sale_order_line_id", + ) + + qty_to_invoice = fields.Float( + string="To Invoice Quantity", + help="The quantity to invoice. If the invoice policy is order, " + "the quantity to invoice is calculated from the ordered quantity. " + "Otherwise, the quantity delivered is used.", + readonly=True, + store=True, + digits="Product Unit of Measure", + compute="_compute_get_to_invoice_qty", + ) + qty_invoiced = fields.Float( + string="Invoiced Quantity", + help="It is the amount invoiced when an invoice is issued", + readonly=True, + store=True, + digits="Product Unit of Measure", + compute="_compute_get_invoice_qty", + compute_sudo=True, + ) + + untaxed_amount_invoiced = fields.Monetary( + string="Untaxed Invoiced Amount", + help="The amount to invoice without taxes in the line of folio", + store=True, + compute="_compute_untaxed_amount_invoiced", + compute_sudo=True, + ) + untaxed_amount_to_invoice = fields.Monetary( + string="Untaxed Amount To Invoice", + help="The invoiced amount without taxes in the line of the folio", + store=True, + compute="_compute_untaxed_amount_to_invoice", + compute_sudo=True, + ) + + currency_id = fields.Many2one( + string="Currency", + help="The currency for the folio", + readonly=True, + store=True, + depends=["folio_id.currency_id"], + related="folio_id.currency_id", + ) + company_id = fields.Many2one( + string="Company", + help="The company in the folio sale line", + readonly=True, + store=True, + index=True, + related="folio_id.company_id", + ) + origin_agency_id = fields.Many2one( + string="Origin Agency", + help="The agency where the folio sale line originates", + comodel_name="res.partner", + domain="[('is_agency', '=', True)]", + compute="_compute_origin_agency_id", + store=True, + index=True, + readonly=False, + ) + # analytic_tag_ids = fields.Many2many( + # string="Analytic Tags", + # comodel_name="account.analytic.tag", + # domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", + # ) + analytic_line_ids = fields.One2many( + string="Analytic lines", + comodel_name="account.analytic.line", + inverse_name="so_line", + ) + is_downpayment = fields.Boolean( + string="Is a down payment", + help="Down payments are made when creating invoices from a folio." + " They are not copied when duplicating a folio.", + ) + + state = fields.Selection( + string="Folio Status", + help="The status of the folio related with folio sale line", + readonly=True, + copy=False, + store=True, + related="folio_id.state", + ) + + display_type = fields.Selection( + string="Display Type", + help="Technical field for UX purpose.", + selection=[("line_section", "Section"), ("line_note", "Note")], + default=False, + ) + + section_id = fields.Many2one( + string="Section", + help="The section of the folio sale line", + comodel_name="folio.sale.line", + compute="_compute_section_id", + index=True, + ) + + service_order = fields.Integer( + string="Service Id", + help="Field to order by service id", + readonly=True, + store=True, + compute="_compute_service_order", + ) + + reservation_order = fields.Integer( + string="Reservation Id", + help="Field to order by reservation id", + readonly=True, + store=True, + compute="_compute_reservation_order", + ) + + date_order = fields.Date( + string="Date", + help="Field to order by service", + readonly=True, + store=True, + compute="_compute_date_order", + ) + default_invoice_to = fields.Many2one( + string="Invoice to", + help="""Indicates the contact to which this line will be + billed by default, if it is not established, + a guest or the generic contact will be used instead""", + comodel_name="res.partner", + ondelete="restrict", + index=True, + ) + autoinvoice_date = fields.Date( + string="Autoinvoice Date", + compute="_compute_autoinvoice_date", + store=True, + ) + + @api.depends( + "folio_id.agency_id", + "reservation_line_ids", + "service_line_ids", + ) + def _compute_origin_agency_id(self): + """ + Set the origin agency if the origin lines channel + match with the agency's channel + """ + for rec in self: + # TODO: ServiceLines agency + if rec.folio_id.agency_id and list( + set(rec.reservation_line_ids.mapped("sale_channel_id.id")) + ) == rec.folio_id.agency_id.mapped("sale_channel_id.id"): + rec.origin_agency_id = rec.folio_id.agency_id + else: + rec.origin_agency_id = False + + @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 + ) + + def _compute_section_id(self): + for record in self: + if record.display_type == "line_section": + record.section_id = record.id + elif record.reservation_id: + record.section_id = record.folio_id.sale_line_ids.filtered( + lambda r: r.reservation_id == record.reservation_id + and r.display_type == "line_section" + ) + else: + record.section_id = False + + @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( + "default_invoice_to", + "invoice_status", + "folio_id.last_checkout", + "reservation_id.checkout", + "service_id.reservation_id.checkout", + ) + def _compute_autoinvoice_date(self): + for record in self: + record.autoinvoice_date = record._get_to_invoice_date() + + def _get_to_invoice_date(self): + self.ensure_one() + partner = self.default_invoice_to + if self.reservation_id: + last_checkout = self.reservation_id.checkout + elif self.service_id and self.service_id.reservation_id: + last_checkout = self.service_id.reservation_id.checkout + else: + last_checkout = self.folio_id.last_checkout + if not last_checkout: + return False + invoicing_policy = ( + self.folio_id.pms_property_id.default_invoicing_policy + if not partner or partner.invoicing_policy == "property" + else partner.invoicing_policy + ) + if invoicing_policy == "manual": + return False + if invoicing_policy == "checkout": + margin_days = ( + self.folio_id.pms_property_id.margin_days_autoinvoice + if not partner or partner.invoicing_policy == "property" + else partner.margin_days_autoinvoice + ) + return last_checkout + timedelta(days=margin_days) + if invoicing_policy == "month_day": + month_day = ( + self.folio_id.pms_property_id.invoicing_month_day + if not partner or partner.invoicing_policy == "property" + else partner.invoicing_month_day + ) + if last_checkout.day <= month_day: + return last_checkout.replace(day=month_day) + else: + return (last_checkout + relativedelta.relativedelta(months=1)).replace( + day=month_day + ) + + @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: + if line.reservation_line_ids: + line.product_uom_qty = len(line.reservation_line_ids) + elif line.service_line_ids: + line.product_uom_qty = sum(line.service_line_ids.mapped("day_qty")) + elif not line.product_uom_qty: + line.product_uom_qty = False + + @api.depends("state") + def _compute_product_uom_readonly(self): + for line in self: + line.product_uom_readonly = line.state in ["sale", "done", "cancel"] + + @api.depends( + "invoice_lines", + "invoice_lines.price_total", + "invoice_lines.move_id.state", + "invoice_lines.move_id.move_type", + ) + def _compute_untaxed_amount_invoiced(self): + """Compute the untaxed amount already invoiced from + the sale order line, taking the refund attached + the so line into account. This amount is computed as + SUM(inv_line.price_subtotal) - SUM(ref_line.price_subtotal) + where + `inv_line` is a customer invoice line linked to the SO line + `ref_line` is a customer credit note (refund) line linked to the SO line + """ + for line in self: + amount_invoiced = 0.0 + for invoice_line in line.invoice_lines: + if invoice_line.move_id.state == "posted": + invoice_date = ( + invoice_line.move_id.invoice_date or fields.Date.today() + ) + if invoice_line.move_id.move_type in ["out_invoice", "out_receipt"]: + amount_invoiced += invoice_line.currency_id._convert( + invoice_line.price_subtotal, + line.currency_id, + line.company_id, + invoice_date, + ) + elif invoice_line.move_id.move_type == "out_refund": + amount_invoiced -= invoice_line.currency_id._convert( + invoice_line.price_subtotal, + line.currency_id, + line.company_id, + invoice_date, + ) + line.untaxed_amount_invoiced = amount_invoiced + + @api.depends( + "state", + "price_reduce", + "product_id", + "untaxed_amount_invoiced", + "product_uom_qty", + ) + def _compute_untaxed_amount_to_invoice(self): + """Total of remaining amount to invoice on the sale order line (taxes excl.) as + total_sol - amount already invoiced + where Total_sol depends on the invoice policy of the product. + + Note: Draft invoice are ignored on purpose, the 'to invoice' amount should + come only from the folio lines. + """ + for line in self: + amount_to_invoice = 0.0 + if line.state != "draft": + # Note: do not use price_subtotal field as it returns + # zero when the ordered quantity is zero. + # It causes problem for expense line (e.i.: ordered qty = 0, + # deli qty = 4, price_unit = 20 ; subtotal is zero), + # but when you can invoice the line, + # you see an amount and not zero. + # Since we compute untaxed amount, we can use directly the price + # reduce (to include discount) without using `compute_all()` + # method on taxes. + price_subtotal = 0.0 + price_subtotal = line.price_reduce * line.product_uom_qty + if len(line.tax_ids.filtered(lambda tax: tax.price_include)) > 0: + # As included taxes are not excluded from the computed subtotal, + # `compute_all()` method has to be called to retrieve + # the subtotal without them. + # `price_reduce_taxexcl` cannot be used as it is computed from + # `price_subtotal` field. (see upper Note) + price_subtotal = line.tax_ids.compute_all( + price_subtotal, + currency=line.folio_id.currency_id, + quantity=line.product_uom_qty, + product=line.product_id, + partner=line.folio_id.partner_id, + )["total_excluded"] + + if any( + line.invoice_lines.mapped(lambda l: l.discount != line.discount) + ): + # In case of re-invoicing with different + # discount we try to calculate manually the + # remaining amount to invoice + amount = 0 + for inv_line in line.invoice_lines: + if ( + len( + inv_line.tax_ids.filtered(lambda tax: tax.price_include) + ) + > 0 + ): + amount += inv_line.tax_ids.compute_all( + inv_line.currency_id._convert( + inv_line.price_unit, + line.currency_id, + line.company_id, + inv_line.date or fields.Date.today(), + round=False, + ) + * inv_line.quantity + )["total_excluded"] + else: + amount += ( + inv_line.currency_id._convert( + inv_line.price_unit, + line.currency_id, + line.company_id, + inv_line.date or fields.Date.today(), + round=False, + ) + * inv_line.quantity + ) + + amount_to_invoice = max(price_subtotal - amount, 0) + else: + amount_to_invoice = price_subtotal - line.untaxed_amount_invoiced + + line.untaxed_amount_to_invoice = amount_to_invoice + + @api.depends("state", "product_uom_qty", "qty_to_invoice", "qty_invoiced") + def _compute_invoice_status(self): + """ + Compute the invoice status of a SO line: + Its if compute based on reservations/services associated status + """ + precision = self.env["decimal.precision"].precision_get( + "Product Unit of Measure" + ) + for line in self: + if line.state == "draft" or line.price_total == 0.0: + line.invoice_status = "no" + # REVIEW: if qty_to_invoice < 0 (invoice qty > sale qty), + # why status to_invoice?? this behavior is copied from sale order + # https://github.com/OCA/OCB/blob/14.0/addons/sale/models/sale.py#L1160 + elif line.qty_to_invoice > 0: + line.invoice_status = "to_invoice" + elif ( + float_compare( + line.qty_invoiced, + line.product_uom_qty, + precision_digits=precision, + ) + >= 0 + ): + line.invoice_status = "invoiced" + else: + line.invoice_status = "no" + + @api.depends("reservation_line_ids", "service_line_ids", "service_id") + def _compute_name(self): + for record in self: + 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("product_uom_qty", "discount", "price_unit", "tax_ids") + def _compute_amount(self): + """ + Compute the amounts of the Sale line. + """ + for line in self: + price = line.price_unit * (1 - (line.discount or 0.0) / 100.0) + taxes = line.tax_ids.compute_all( + price, + line.folio_id.currency_id, + line.product_uom_qty, + product=line.product_id, + ) + line.update( + { + "price_tax": sum( + t.get("amount", 0.0) for t in taxes.get("taxes", []) + ), + "price_total": taxes["total_included"], + "price_subtotal": taxes["total_excluded"], + } + ) + if self.env.context.get( + "import_file", False + ) and not self.env.user.user_has_groups("account.group_account_manager"): + line.tax_ids.invalidate_cache( + ["invoice_repartition_line_ids"], [line.tax_ids.id] + ) + + @api.depends("reservation_id.tax_ids", "service_id.tax_ids") + def _compute_tax_ids(self): + for record in self: + if not record.display_type: + record.tax_ids = ( + record.service_id.tax_ids + if record.service_id + else record.reservation_id.tax_ids + ) + else: + record.tax_ids = False + + @api.depends( + "service_id", + "service_id.service_line_ids", + "service_id.service_line_ids.discount", + ) + def _compute_discount(self): + """ + Only in services without room we compute discount, + and this services only have one service line + """ + for record in self: + if record.service_id and not record.service_id.reservation_id: + record.discount = record.service_id.service_line_ids.mapped("discount")[ + 0 + ] + elif not record.discount: + record.discount = 0 + + @api.depends("reservation_id.room_type_id", "service_id.product_id") + def _compute_product_id(self): + for record in self.filtered("display_type"): + 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 + else: + record.product_id = False + + # @api.depends('product_id', 'folio_id.state', 'qty_invoiced', 'qty_delivered') + # def _compute_product_updatable(self): + # for line in self: + # if line.state in ['done', 'cancel'] or ( + # line.state == 'sale' and ( + # line.qty_invoiced > 0 or line.qty_delivered > 0)): + # line.product_updatable = False + # else: + # line.product_updatable = True + + # no trigger product_id.invoice_policy to avoid retroactively changing SO + @api.depends("qty_invoiced", "product_uom_qty", "folio_id.state") + def _compute_get_to_invoice_qty(self): + """ + Compute the quantity to invoice. + If the invoice policy is order, the quantity to invoice is + calculated from the ordered quantity. + Otherwise, the quantity delivered is used. + """ + for line in self: + if line.folio_id.state not in ["draft"]: + line.qty_to_invoice = line.product_uom_qty - line.qty_invoiced + else: + line.qty_to_invoice = 0 + + @api.depends( + "invoice_lines.move_id.state", + "invoice_lines.quantity", + "untaxed_amount_to_invoice", + ) + def _compute_get_invoice_qty(self): + for line in self: + qty_invoiced = 0.0 + for invoice_line in line.invoice_lines: + if invoice_line.move_id.state != "cancel": + if invoice_line.move_id.move_type in ["out_invoice", "out_receipt"]: + qty_invoiced += invoice_line.product_uom_id._compute_quantity( + invoice_line.quantity, line.product_uom + ) + elif invoice_line.move_id.move_type == "out_refund": + qty_invoiced -= invoice_line.product_uom_id._compute_quantity( + invoice_line.quantity, line.product_uom + ) + line.qty_invoiced = qty_invoiced + + @api.depends("price_unit", "discount") + def _compute_get_price_reduce(self): + for line in self: + line.price_reduce = line.price_unit * (1.0 - line.discount / 100.0) + + @api.depends("price_total", "product_uom_qty") + def _compute_get_price_reduce_tax(self): + for line in self: + line.price_reduce_taxinc = ( + line.price_total / line.product_uom_qty if line.product_uom_qty else 0.0 + ) + + @api.depends("price_subtotal", "product_uom_qty") + def _compute_get_price_reduce_notax(self): + for line in self: + line.price_reduce_taxexcl = ( + line.price_subtotal / line.product_uom_qty + if line.product_uom_qty + else 0.0 + ) + + # @api.model + # def _prepare_add_missing_fields(self, values): + # """ Deduce missing required fields from the onchange """ + # res = {} + # onchange_fields = ['name', 'price_unit', 'product_uom', 'tax_ids'] + # if values.get('folio_id') and values.get('product_id') and any( + # f not in values for f in onchange_fields + # ): + # line = self.new(values) + # line.product_id_change() + # for field in onchange_fields: + # if field not in values: + # res[field] = line._fields[field].convert_to_write( + # line[field], line + # ) + # return res + + # @api.model_create_multi + # def create(self, vals_list): + # for values in vals_list: + # if values.get('display_type', self.default_get( + # ['display_type'])['display_type'] + # ): + # values.update(product_id=False, price_unit=0, + # product_uom_qty=0, product_uom=False, + # customer_lead=0) + + # values.update(self._prepare_add_missing_fields(values)) + + # lines = super().create(vals_list) + # for line in lines: + # if line.product_id and line.folio_id.state == 'sale': + # msg = _("Extra line with %s ") % (line.product_id.display_name,) + # line.folio_id.message_post(body=msg) + # # create an analytic account if at least an expense product + # if line.product_id.expense_policy not in [False, 'no'] and \ + # not line.folio_id.analytic_account_id: + # line.folio_id._create_analytic_account() + # return lines + + # _sql_constraints = [ + # ('accountable_required_fields', + # "CHECK(display_type IS NOT NULL OR \ + # (product_id IS NOT NULL AND product_uom IS NOT NULL))", + # "Missing required fields on accountable sale order line."), + # ('non_accountable_null_fields', + # "CHECK(display_type IS NULL OR (product_id IS NULL AND \ + # price_unit = 0 AND product_uom_qty = 0 AND \ + # product_uom IS NULL AND customer_lead = 0))", + # "Forbidden values on non-accountable sale order line"), + # ] + @api.model + def _name_search( + self, name, args=None, operator="ilike", limit=100, name_get_uid=None + ): + if operator in ("ilike", "like", "=", "=like", "=ilike"): + args = expression.AND( + [ + args or [], + ["|", ("folio_id.name", operator, name), ("name", operator, name)], + ] + ) + return super(FolioSaleLine, self)._name_search( + name, args=args, operator=operator, limit=limit, name_get_uid=name_get_uid + ) + + def name_get(self): + result = [] + for so_line in self.sudo(): + name = "{} - {}".format( + so_line.folio_id.name, + so_line.name and so_line.name.split("\n")[0] or so_line.product_id.name, + ) + result.append((so_line.id, name)) + return result + + @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 = 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 += ( + babel.dates.format_date( + date=date, format="MMMM y", locale=get_lang(self.env).code + ) + + ": " + ) + name += date.strftime("%d") + month = date.month + else: + name += ", " + date.strftime("%d") + + 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 += ( + babel.dates.format_date( + date=date, format="MMMM y", locale=get_lang(self.env).code + ) + + ": " + ) + name += date.strftime("%d") + month = date.month + else: + name += ", " + date.strftime("%d") + return "{} ({}).".format(service_id.name, name) + else: + return service_id.name + + def _get_invoice_line_sequence(self, new=0, old=0): + """ + Method intended to be overridden in third-party + module if we want to prevent the resequencing of invoice lines. + + :param int new: the new line sequence + :param int old: the old line sequence + + :return: the sequence of the SO line, by default the new one. + """ + return new or old + + def _mens_update_line_quantity(self, values): + folios = self.mapped("folio_id") + for order in folios: + order_lines = self.filtered(lambda x: x.folio_id == order) + msg = "" + _("The ordered quantity has been updated.") + "
    " + for line in order_lines: + msg += "
  • %s:
    " % line.product_id.display_name + msg += ( + _( + "Ordered Quantity: %(old_qty)s -> %(new_qty)s", + old_qty=line.product_uom_qty, + new_qty=values["product_uom_qty"], + ) + + "
    " + ) + # if line.product_id.type in ('consu', 'product'): + # msg += _("Delivered Quantity: %s", line.qty_delivered) + "
    " + msg += _("Invoiced Quantity: %s", line.qty_invoiced) + "
    " + msg += "
" + order.message_post(body=msg) + + def write(self, values): + # Prevent writing on a locked Folio or folio sale lines invoiced. + protected_fields = self._get_protected_fields() + protected_fields_modified_list = list( + set(protected_fields) & set(values.keys()) + ) + fields_modified = self.env["ir.model.fields"].search( + [("name", "in", protected_fields_modified_list), ("model", "=", self._name)] + ) + if fields_modified or "product_uom_qty" in values: + # If value is float, we need to round it to compare with the original value + for field in fields_modified: + if field.ttype in ["float", "monetary"] and field.name in values: + values[field.name] = round(values[field.name], 2) + has_locked_folio = "done" in self.mapped( + "folio_id.state" + ) or self.invoice_lines.filtered( + lambda l: l.move_id.state == "posted" + and l.move_id.move_type == "out_invoice" + and l.move_id.payment_state != "reversed" + ) + if has_locked_folio: + # We check that dont reduced the invoiced quantity in locked folios + if "product_uom_qty" in values and any( + line.qty_invoiced > values["product_uom_qty"] for line in self + ): + raise UserError( + _( + """You cannot reduce the invoiced quantity below + the quantity already invoiced.""" + ) + ) + # We check that dont modified the protected fields in locked folios + if self.filtered( + lambda l: any( + values.get(field.name) != getattr(l, field.name) + for field in fields_modified + ) + ): + raise UserError( + _( + """It is forbidden to modify the following fields + in a locked folio (fields already invoiced):\n%s""" + ) + % "\n".join(fields_modified.mapped("field_description")) + ) + # If has draft invoices, we need to update the invoice lines + if "draft" in self.mapped("invoice_lines.move_id.state"): + draft_moves = self.env["account.move"] + if "product_uom_qty" in values: + for line in self: + if line.qty_invoiced > values["product_uom_qty"]: + raise UserError( + _( + "This quantity was already invoiced." + " You must reduce the invoiced quantity first." + ) + ) + for line in self.filtered( + lambda l: not l.display_type + and not ( + l.invoice_lines.filtered( + lambda inv_line: inv_line.move_id.state == "draft" + ) + ) + ): + draft_moves |= line.invoice_lines.move_id + mapped_fields = self._get_mapped_move_line_fields() + move_line_vals = [ + ( + 1, + line.invoice_lines[0].id, + { + mapped_fields[field]: values[field] + for field in fields_modified.mapped("name") + }, + ) + ] + # Add invoice line name in values to avoid + # overwriting the name by _move_autocomplete_invoice_lines_values + if not move_line_vals[0][2].get("name"): + move_line_vals[0][2]["name"] = line.name + line[0].invoice_lines.move_id.write( + {"invoice_line_ids": move_line_vals} + ) + if "product_uom_qty" in values: + line._mens_update_line_quantity(values) + # avoid draft invoice naming compute + if draft_moves: + draft_moves.name = "/" + + result = super(FolioSaleLine, self).write(values) + return result + + def _prepare_invoice_line(self, qty=False, **optional_values): + """ + Prepare the dict of values to create the new invoice line for a folio sale line. + + :param qty: float quantity to invoice + :param optional_values: any parameter that + should be added to the returned invoice line + """ + self.ensure_one() + if self.is_downpayment: + downpayment_invoice = self.folio_id.move_ids.filtered( + lambda x: x.payment_state != "reversed" + and x.move_type == "out_invoice" + and x.line_ids.filtered(lambda l: l.folio_line_ids == self) + ) + name = self.name + " (" + downpayment_invoice.name + ")" + elif self.display_type == "line_section" and self.reservation_id: + name = self.name + " - " + self.reservation_id.rooms + else: + name = self.name + res = { + "sequence": self.sequence, + "name": name, + "product_id": self.product_id.id, + "product_uom_id": self.product_uom.id, + "quantity": qty if qty else self.qty_to_invoice, + "discount": self.discount, + "price_unit": self.price_unit, + "tax_ids": [(6, 0, self.tax_ids.ids)], + # "analytic_account_id": self.folio_id.analytic_account_id.id, + # "analytic_tag_ids": [(6, 0, self.analytic_tag_ids.ids)], + "folio_line_ids": [(6, 0, [self.id])], + "name_changed_by_user": False, + } + if self.display_type: + res["display_type"] = self.display_type + if optional_values: + res.update(optional_values) + if self.display_type: + res["account_id"] = False + return res + + def unlink(self): + for record in self: + if record.qty_invoiced > 0: + # If the invoice line is in draft, unlink it, else raise an error + if record.invoice_lines.filtered(lambda l: l.move_id.state == "draft"): + moves = record.invoice_lines.mapped("move_id") + record.invoice_lines.with_context( + check_move_validity=False + ).filtered(lambda l: l.move_id.state == "draft").unlink() + moves._recompute_dynamic_lines( + recompute_all_taxes=True, recompute_tax_base_amount=True + ) + else: + raise UserError( + _( + "You cannot delete a sale order line once a " + "invoice has been created from it." + ) + ) + return super(FolioSaleLine, self).unlink() + + def _get_real_price_currency(self, product, rule_id, qty, uom, pricelist_id): + """Retrieve the price before applying the pricelist + :param obj product: object of current product record + :parem float qty: total quentity of product + :param tuple price_and_rule: tuple(price, suitable_rule) + coming from pricelist computation + :param obj uom: unit of measure of current folio line + :param integer pricelist_id: pricelist id of folio""" + PricelistItem = self.env["product.pricelist.item"] + field_name = "lst_price" + currency_id = None + product_currency = product.currency_id + if rule_id: + pricelist_item = PricelistItem.browse(rule_id) + if pricelist_item.pricelist_id.discount_policy == "without_discount": + while ( + pricelist_item.base == "pricelist" + and pricelist_item.base_pricelist_id + and pricelist_item.base_pricelist_id.discount_policy + == "without_discount" + ): + price, rule_id = pricelist_item.base_pricelist_id.with_context( + uom=uom.id + ).get_product_price_rule(product, qty, self.folio_id.partner_id) + pricelist_item = PricelistItem.browse(rule_id) + + if pricelist_item.base == "standard_price": + field_name = "standard_price" + product_currency = product.cost_currency_id + elif ( + pricelist_item.base == "pricelist" and pricelist_item.base_pricelist_id + ): + field_name = "price" + product = product.with_context( + pricelist=pricelist_item.base_pricelist_id.id + ) + product_currency = pricelist_item.base_pricelist_id.currency_id + currency_id = pricelist_item.pricelist_id.currency_id + + if not currency_id: + currency_id = product_currency + cur_factor = 1.0 + else: + if currency_id.id == product_currency.id: + cur_factor = 1.0 + else: + cur_factor = currency_id._get_conversion_rate( + product_currency, + currency_id, + self.company_id or self.env.company, + self.folio_id.date_order or fields.Date.today(), + ) + + product_uom = self.env.context.get("uom") or product.uom_id.id + if uom and uom.id != product_uom: + # the unit price is in a different uom + uom_factor = uom._compute_price(1.0, product.uom_id) + else: + uom_factor = 1.0 + + return product[field_name] * uom_factor * cur_factor, currency_id + + def _get_protected_fields(self): + return [ + "product_id", + "price_unit", + "product_uom", + "tax_ids", + # "analytic_tag_ids", + "discount", + ] + + def _get_mapped_move_line_fields(self): + return { + "product_id": "product_id", + "price_unit": "price_unit", + "product_uom": "product_uom_id", + "tax_ids": "tax_ids", + # "analytic_tag_ids": "analytic_tag_ids", + "discount": "discount", + } diff --git a/pms/models/ir_config_parameter.py b/pms/models/ir_config_parameter.py new file mode 100644 index 0000000000..a561369acd --- /dev/null +++ b/pms/models/ir_config_parameter.py @@ -0,0 +1,22 @@ +from odoo import _, api, models +from odoo.exceptions import ValidationError + + +class IrConfigParameter(models.Model): + _inherit = "ir.config_parameter" + + def unlink(self): + for record in self: + if ( + record.key == "product.product_pricelist_setting" + and record.value == "advanced" + ): + raise ValidationError(_("Cannot delete this parameter")) + return super().unlink() + + @api.constrains("key", "value") + def check_value(self): + if self.key == "product.product_pricelist_setting" and self.value != "advanced": + raise ValidationError( + _("The parameter Advanced price rules cannot be modified") + ) diff --git a/pms/models/ir_http.py b/pms/models/ir_http.py new file mode 100644 index 0000000000..603a58c16f --- /dev/null +++ b/pms/models/ir_http.py @@ -0,0 +1,50 @@ +# Copyright 2019 Pablo Quesada +# Copyright 2019 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models +from odoo.http import request + + +class IrHttp(models.AbstractModel): + _inherit = "ir.http" + + def session_info(self): + res = super().session_info() + user = request.env.user + res.update( + { + # current_pms_property should be default_property + "user_pms_properties": { + "current_pms_property": ( + user.pms_property_id.id, + user.pms_property_id.name, + ), + # TODO: filter all properties based on + # the current set of active companies + "allowed_pms_properties": [ + (property.id, property.name) + for property in user.pms_property_ids + ], + }, + "display_switch_pms_property_menu": len(user.pms_property_ids) > 1, + } + ) + # TODO: This user context update should be placed in other function ¿? + res["user_context"].update( + { + "allowed_pms_property_ids": [ + (property.id) for property in user.pms_property_ids + ] + } + ) + # TODO: update current_company based on current_pms_property + # if user.pms_property_id.company_id in user.company_ids: + # user.company_id = user.pms_property_id.company_id + # res['company_id'] = user.pms_property_id.company_id.id + # else: + # raise MissingError( + # _("Wrong property and company access settings for this user. " + # "Please review property and company for user %s") % user.name) + + return res diff --git a/pms/models/ir_pms_property.py b/pms/models/ir_pms_property.py new file mode 100644 index 0000000000..1e6c9acb83 --- /dev/null +++ b/pms/models/ir_pms_property.py @@ -0,0 +1,95 @@ +from odoo import fields, models + + +class IrPmsProperty(models.Model): + _name = "ir.pms.property" + _description = "IrPmsProperty" + pms_property_id = fields.Many2one( + string="Properties", + help="", + comodel_name="pms.property", + index=True, + ) + model_id = fields.Many2one(string="Model", comodel_name="ir.model", index=True) + field_id = fields.Many2one( + string="Field", + comodel_name="ir.model.fields", + index=True, + ) + record = fields.Integer(string="Record Id") + + value_integer = fields.Integer(string="Integer Field Value") + + value_float = fields.Float(string="Float Field Value") + + value_reference = fields.Text(string="Reference Field Value") + + def get_field_value( + self, pms_property_id, model_name, field_name, record_id, value_type + ): + model = self.env["ir.model"].search([("model", "=", model_name)]) + if model: + field_id = self.env["ir.model.fields"].search( + [("name", "=", field_name), ("model_id", "=", model.id)] + ) + ir_pms_property = self.env["ir.pms.property"].search( + [ + ("pms_property_id", "=", pms_property_id), + ("field_id", "=", field_id[0].id), + ("record", "=", record_id), + ] + ) + if ir_pms_property: + if value_type == int: + value = ir_pms_property.value_integer + elif value_type == float: + value = ir_pms_property.value_float + else: + index_bracket = ir_pms_property.value_reference.index("(") + index_comma = ir_pms_property.value_reference.index(",") + model_name = ir_pms_property.value_reference[:index_bracket] + resource_id = ir_pms_property.value_reference[ + index_bracket + 1 : index_comma + ] + value = self.env[model_name].browse(int(resource_id)) + return value + return False + + def set_field_value( + self, pms_property_id, model_name, field_name, record_id, value + ): + model = self.env["ir.model"].search([("model", "=", model_name)]) + if model: + field_id = self.env["ir.model.fields"].search( + [("name", "=", field_name), ("model_id", "=", model.id)] + ) + ir_pms_property = self.env["ir.pms.property"].search( + [ + ("pms_property_id", "=", pms_property_id), + ("field_id", "=", field_id[0].id), + ("record", "=", record_id), + ] + ) + if type(value) == int: + value_type = "value_integer" + elif type(value) == float: + value_type = "value_float" + else: + value_type = "value_reference" + value = str(value) + if ir_pms_property: + ir_pms_property.write( + { + value_type: value, + } + ) + else: + self.env["ir.pms.property"].create( + { + "pms_property_id": pms_property_id, + "model_id": model.id, + "field_id": field_id[0].id, + value_type: value, + "record": record_id, + } + ) diff --git a/pms/models/mail_compose_message.py b/pms/models/mail_compose_message.py new file mode 100644 index 0000000000..24af8478ad --- /dev/null +++ b/pms/models/mail_compose_message.py @@ -0,0 +1,20 @@ +# Copyright 2017 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models + + +class MailComposeMessage(models.TransientModel): + _inherit = "mail.compose.message" + + def send_mail(self, auto_commit=False): + res = super(MailComposeMessage, self).send_mail(auto_commit=auto_commit) + if ( + self._context.get("default_model") == "pms.folio" + and self._context.get("active_model") == "pms.reservation" + ): + folio = self.env["pms.folio"].browse(self._context.get("default_res_id")) + reservations = folio.reservation_ids + for reservation in reservations: + reservation.to_send_confirmation_mail = False + return res diff --git a/pms/models/payment_acquirer.py b/pms/models/payment_acquirer.py new file mode 100644 index 0000000000..6c5b943c26 --- /dev/null +++ b/pms/models/payment_acquirer.py @@ -0,0 +1,21 @@ +# Copyright 2009-2020 Noviat +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class PaymentAcquirer(models.Model): + _inherit = "payment.provider" + _check_pms_properties_auto = True + + pms_property_ids = fields.Many2many( + string="Properties", + help="Properties with access to the element;" + " if not set, all properties can access", + required=False, + comodel_name="pms.property", + relation="pms_acquirer_property_rel", + column1="acquirer_id", + column2="property_id", + check_pms_properties=True, + ) diff --git a/pms/models/payment_transaction.py b/pms/models/payment_transaction.py new file mode 100644 index 0000000000..4b10181310 --- /dev/null +++ b/pms/models/payment_transaction.py @@ -0,0 +1,53 @@ +from odoo import _, api, fields, models + + +class PaymentTransaction(models.Model): + _inherit = "payment.transaction" + _check_pms_properties_auto = True + + folio_ids = fields.Many2many( + string="Folios", + comodel_name="pms.folio", + ondelete="cascade", + relation="payment_transaction_folio_rel", + column1="payment_transaction_id", + column2="folio_id", + ) + + def _create_payment(self, add_payment_vals=False): + self.ensure_one() + if not add_payment_vals: + add_payment_vals = {} + if self.folio_ids: + add_payment_vals["folio_ids"] = [(6, 0, self.folio_ids.ids)] + return super(PaymentTransaction, self)._create_payment(add_payment_vals) + + def render_folio_button( + self, folio, submit_txt=None, render_values=None, custom_amount=None + ): + values = { + "partner_id": folio.partner_id.id, + "type": self.type, + } + if render_values: + values.update(render_values) + return ( + self.acquirer_id.with_context( + submit_class="btn btn-primary", submit_txt=submit_txt or _("Pay Now") + ) + .sudo() + .render( + self.reference, + custom_amount or folio.pending_amount, + folio.currency_id.id, + values=values, + ) + ) + + @api.model + def _compute_reference_prefix(self, values): + res = super(PaymentTransaction, self)._compute_reference_prefix(values) + if not res and values and values.get("folio_ids"): + folios = self.new({"folio_ids": values["folio_ids"]}).folio_ids + return "".join(folios.mapped("name"))[-9:] + return None diff --git a/pms/models/pms_amenity.py b/pms/models/pms_amenity.py new file mode 100644 index 0000000000..39936cee50 --- /dev/null +++ b/pms/models/pms_amenity.py @@ -0,0 +1,47 @@ +# 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 + + +class PmsRoomAmenity(models.Model): + _name = "pms.amenity" + _description = "Room amenity" + _check_pms_properties_auto = True + + active = fields.Boolean( + string="Active", + help="Determines if amenity is active", + default=True, + ) + name = fields.Char( + string="Amenity Name", + help="Amenity Name", + required=True, + translate=True, + ) + pms_property_ids = fields.Many2many( + string="Properties", + help="Properties with access to the element;" + " if not set, all properties can access", + comodel_name="pms.property", + ondelete="restrict", + relation="pms_amenity_pms_property_rel", + column1="amenity_id", + column2="pms_property_id", + check_pms_properties=True, + ) + pms_amenity_type_id = fields.Many2one( + string="Amenity Category", + help="Segment the amenities by categories (multimedia, comfort, etc ...)", + comodel_name="pms.amenity.type", + index=True, + check_pms_properties=True, + ) + default_code = fields.Char( + string="Internal Reference", help="Internal unique identifier of the amenity" + ) + is_add_code_room_name = fields.Boolean( + string="Add in room name", + help="True if the Internal Reference should appear in the display name of the rooms", + ) diff --git a/pms/models/pms_amenity_type.py b/pms/models/pms_amenity_type.py new file mode 100644 index 0000000000..ec982d55dc --- /dev/null +++ b/pms/models/pms_amenity_type.py @@ -0,0 +1,40 @@ +# 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 + + +class PmsRoomAmenityType(models.Model): + _name = "pms.amenity.type" + _description = "Amenity Type" + _check_pms_properties_auto = True + + active = fields.Boolean( + string="Active", + help="Determines if amenity type is active", + default=True, + ) + name = fields.Char( + string="Amenity Type Name", + help="Amenity Type Name", + required=True, + translate=True, + ) + pms_property_ids = fields.Many2many( + string="Properties", + help="Properties with access to the element;" + " if not set, all properties can access", + comodel_name="pms.property", + ondelete="restrict", + relation="pms_amenity_type_pms_property_rel", + column1="amenity_type_id", + column2="pms_property_id", + check_pms_properties=True, + ) + pms_amenity_ids = fields.One2many( + string="Amenities In This Category", + help="Amenities included in this type", + comodel_name="pms.amenity", + inverse_name="pms_amenity_type_id", + check_pms_properties=True, + ) diff --git a/pms/models/pms_automated_mails.py b/pms/models/pms_automated_mails.py new file mode 100644 index 0000000000..36eb0a4368 --- /dev/null +++ b/pms/models/pms_automated_mails.py @@ -0,0 +1,9 @@ +from odoo import fields, models + + +class PmsAutomatedMails(models.Model): + _name = "pms.automated.mails" + _description = "Automatic Mails" + + # TODO: Model to delete + name = fields.Char(string="Name", help="Name of the automated mail.", required=True) diff --git a/pms/models/pms_availability.py b/pms/models/pms_availability.py new file mode 100644 index 0000000000..ac4ad1e18a --- /dev/null +++ b/pms/models/pms_availability.py @@ -0,0 +1,283 @@ +# Copyright 2021 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import datetime + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class PmsAvailability(models.Model): + _name = "pms.availability" + _description = "Room type availability per day" + _check_pms_properties_auto = True + + room_type_id = fields.Many2one( + string="Room Type", + help="Room type for which availability is indicated", + readonly=True, + required=True, + index=True, + comodel_name="pms.room.type", + ondelete="cascade", + check_pms_properties=True, + ) + date = fields.Date( + string="Date", + help="Date for which availability applies", + readonly=True, + required=True, + ) + pms_property_id = fields.Many2one( + string="Property", + help="Property to which the availability is directed", + readonly=True, + required=True, + index=True, + comodel_name="pms.property", + ondelete="restrict", + check_pms_properties=True, + ) + reservation_line_ids = fields.One2many( + string="Reservation Lines", + help="They are the lines of the reservation into a reservation," + "they corresponds to the nights", + readonly=True, + comodel_name="pms.reservation.line", + inverse_name="avail_id", + check_pms_properties=True, + ) + avail_rule_ids = fields.One2many( + string="Avail record rules", + comodel_name="pms.availability.plan.rule", + inverse_name="avail_id", + check_pms_properties=True, + ) + real_avail = fields.Integer( + string="Real Avail", + help="", + store=True, + readonly=True, + compute="_compute_real_avail", + ) + parent_avail_id = fields.Many2one( + string="Parent Avail", + help="Parent availability for this availability", + comodel_name="pms.availability", + ondelete="restrict", + compute="_compute_parent_avail_id", + store=True, + index=True, + check_pms_properties=True, + ) + child_avail_ids = fields.One2many( + string="Child Avails", + help="Child availabilities for this availability", + comodel_name="pms.availability", + inverse_name="parent_avail_id", + compute="_compute_child_avail_ids", + store=True, + check_pms_properties=True, + ) + + _sql_constraints = [ + ( + "room_type_registry_unique", + "unique(room_type_id, date, pms_property_id)", + "Only can exists one availability in the same \ + day for the same room type!", + ) + ] + + @api.depends( + "reservation_line_ids", + "reservation_line_ids.occupies_availability", + "room_type_id.total_rooms_count", + "parent_avail_id", + "parent_avail_id.reservation_line_ids", + "parent_avail_id.reservation_line_ids.occupies_availability", + "child_avail_ids", + "child_avail_ids.reservation_line_ids", + "child_avail_ids.reservation_line_ids.occupies_availability", + ) + def _compute_real_avail(self): + for record in self: + Rooms = self.env["pms.room"] + total_rooms = Rooms.search_count( + [ + ("active", "=", True), + ("room_type_id", "=", record.room_type_id.id), + ("pms_property_id", "=", record.pms_property_id.id), + ] + ) + room_ids = record.room_type_id.room_ids.filtered( + lambda r: r.pms_property_id == record.pms_property_id and r.active + ).ids + count_rooms_not_avail = len( + record.get_rooms_not_avail( + checkin=record.date, + checkout=record.date + datetime.timedelta(1), + room_ids=room_ids, + pms_property_id=record.pms_property_id.id, + ) + ) + record.real_avail = total_rooms - count_rooms_not_avail + + @api.depends("reservation_line_ids", "reservation_line_ids.room_id") + def _compute_parent_avail_id(self): + for record in self: + parent_rooms = record.room_type_id.mapped("room_ids.parent_id") + if parent_rooms: + parent_room_ids = parent_rooms.filtered( + lambda r: r.pms_property_id == record.pms_property_id + ).ids + for room_id in parent_room_ids: + room = self.env["pms.room"].browse(room_id) + parent_avail = self.env["pms.availability"].search( + [ + ("date", "=", record.date), + ("room_type_id", "=", room.room_type_id.id), + ("pms_property_id", "=", record.pms_property_id.id), + ] + ) + if parent_avail: + record.parent_avail_id = parent_avail + else: + record.parent_avail_id = self.env["pms.availability"].create( + { + "date": record.date, + "room_type_id": room.room_type_id.id, + "pms_property_id": record.pms_property_id.id, + } + ) + + @api.depends("reservation_line_ids", "reservation_line_ids.room_id") + def _compute_child_avail_ids(self): + for record in self: + child_rooms = record.room_type_id.mapped("room_ids.child_ids") + if child_rooms: + child_room_ids = child_rooms.filtered( + lambda r: r.pms_property_id == record.pms_property_id + ).ids + for room_id in child_room_ids: + room = self.env["pms.room"].browse(room_id) + child_avail = self.env["pms.availability"].search( + [ + ("date", "=", record.date), + ("room_type_id", "=", room.room_type_id.id), + ("pms_property_id", "=", record.pms_property_id.id), + ] + ) + if child_avail: + record.child_avail_ids = [(4, child_avail.id)] + else: + record.child_avail_ids = [ + ( + 0, + 0, + { + "date": record.date, + "room_type_id": room.room_type_id.id, + "pms_property_id": record.pms_property_id.id, + }, + ) + ] + + @api.model + def get_rooms_not_avail( + self, checkin, checkout, room_ids, pms_property_id, current_lines=False + ): + RoomLines = self.env["pms.reservation.line"] + rooms = self.env["pms.room"].browse(room_ids) + occupied_room_ids = [] + for room in rooms.filtered("parent_id"): + if self.get_occupied_parent_rooms( + room=room.parent_id, + checkin=checkin, + checkout=checkout, + ): + occupied_room_ids.append(room.id) + for room in rooms.filtered("child_ids"): + if self.get_occupied_child_rooms( + rooms=room.child_ids, + checkin=checkin, + checkout=checkout, + ): + occupied_room_ids.append(room.id) + occupied_room_ids.extend( + RoomLines.search( + [ + ("date", ">=", checkin), + ("date", "<=", checkout - datetime.timedelta(1)), + ("room_id", "in", room_ids), + ("pms_property_id", "=", pms_property_id), + ("occupies_availability", "=", True), + ("id", "not in", current_lines if current_lines else []), + ] + ).mapped("room_id.id") + ) + return occupied_room_ids + + @api.model + def get_occupied_parent_rooms(self, room, checkin, checkout): + RoomLines = self.env["pms.reservation.line"] + if ( + RoomLines.search_count( + [ + ("date", ">=", checkin), + ("date", "<=", checkout - datetime.timedelta(1)), + ("room_id", "=", room.id), + ("occupies_availability", "=", True), + ] + ) + > 0 + ): + return True + if room.parent_id: + return self.get_occupied_parent_rooms( + room=room.parent_id, checkin=checkin, checkout=checkout + ) + return False + + @api.model + def get_occupied_child_rooms(self, rooms, checkin, checkout): + RoomLines = self.env["pms.reservation.line"] + mapped_properties = list(set(rooms.mapped("pms_property_id.id"))) + if len(mapped_properties) > 1: + raise ValidationError(_("Rooms shared between different properties")) + + if ( + RoomLines.search_count( + [ + ("date", ">=", checkin), + ("date", "<=", checkout - datetime.timedelta(1)), + ("room_id", "in", rooms.ids), + ("occupies_availability", "=", True), + ] + ) + > 0 + ): + return True + for room in rooms.filtered("child_ids"): + if self.get_occupied_child_rooms( + rooms=room.child_ids, + checkin=checkin, + checkout=checkout, + ): + return True + return False + + @api.constrains( + "room_type_id", + "pms_property_id", + ) + def _check_property_integrity(self): + for rec in self: + if rec.pms_property_id and rec.room_type_id: + if ( + rec.room_type_id.pms_property_ids.ids + and rec.pms_property_id.id + not in rec.room_type_id.pms_property_ids.ids + ): + raise ValidationError( + _("Property not allowed on availability day compute") + ) diff --git a/pms/models/pms_availability_plan.py b/pms/models/pms_availability_plan.py new file mode 100644 index 0000000000..133418e4a0 --- /dev/null +++ b/pms/models/pms_availability_plan.py @@ -0,0 +1,123 @@ +# Copyright 2017 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import datetime + +from odoo import api, fields, models +from odoo.tools import DEFAULT_SERVER_DATE_FORMAT + + +class PmsAvailabilityPlan(models.Model): + """The room type availability is used as a daily availability plan for room types + and therefore is related only with one property.""" + + _name = "pms.availability.plan" + _description = "Reservation availability plan" + _check_pms_properties_auto = True + + @api.model + def _get_default_pms_property(self): + return self.env.user.get_active_property_ids()[0] or None + + name = fields.Char( + string="Availability Plan Name", help="Name of availability plan", required=True + ) + pms_property_ids = fields.Many2many( + string="Properties", + help="Properties with access to the element;" + " if not set, all properties can access", + comodel_name="pms.property", + ondelete="restrict", + relation="pms_availability_plan_pms_property_rel", + column1="availability_plan_id", + column2="pms_property_id", + check_pms_properties=True, + ) + pms_pricelist_ids = fields.One2many( + string="Pricelists", + help="Pricelists of the availability plan ", + comodel_name="product.pricelist", + inverse_name="availability_plan_id", + check_pms_properties=True, + domain="[('is_pms_available', '=', True)]", + ) + + rule_ids = fields.One2many( + string="Availability Rules", + help="Rules in a availability plan", + comodel_name="pms.availability.plan.rule", + inverse_name="availability_plan_id", + check_pms_properties=True, + ) + + active = fields.Boolean( + string="Active", + help="If unchecked, it will allow you to hide the " + "Availability plan without removing it.", + default=True, + ) + + @classmethod + def any_rule_applies(cls, checkin, checkout, item): + 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() + reservation_len = (checkout - checkin).days + return any( + [ + (0 < item.max_stay < reservation_len), + (0 < item.min_stay > reservation_len), + (0 < item.max_stay_arrival < reservation_len and checkin == item.date), + (0 < item.min_stay_arrival > reservation_len and checkin == item.date), + item.closed, + (item.closed_arrival and checkin == item.date), + (item.closed_departure and checkout == item.date), + (item.quota == 0 or item.max_avail == 0), + ] + ) + + @api.model + def update_quota( + self, + pricelist_id, + room_type_id, + date, + pms_property_id, + ): + if pricelist_id and room_type_id and date: + rule = self.env["pms.availability.plan.rule"].search( + [ + ("availability_plan_id.pms_pricelist_ids", "in", pricelist_id), + ("room_type_id", "=", room_type_id), + ("date", "=", date), + ("pms_property_id", "=", pms_property_id), + ] + ) + # applies a rule + if rule: + rule.ensure_one() + if rule and rule.quota != -1 and rule.quota > 0: + rule.quota -= 1 + return True + return False + + # Action methods + def open_massive_changes_wizard(self): + + if self.ensure_one(): + return { + "view_type": "form", + "view_mode": "form", + "name": "Massive changes on Availability Plan: " + self.name, + "res_model": "pms.massive.changes.wizard", + "target": "new", + "type": "ir.actions.act_window", + "context": { + "availability_plan_id": self.id, + }, + } diff --git a/pms/models/pms_availability_plan_rule.py b/pms/models/pms_availability_plan_rule.py new file mode 100644 index 0000000000..5c75bce855 --- /dev/null +++ b/pms/models/pms_availability_plan_rule.py @@ -0,0 +1,195 @@ +# Copyright 2017 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class PmsAvailabilityPlanRule(models.Model): + _name = "pms.availability.plan.rule" + _description = "Reservation rule by day" + _check_pms_properties_auto = True + + availability_plan_id = fields.Many2one( + string="Availability Plan", + help="The availability plan that include the Availabilty Rule", + index=True, + comodel_name="pms.availability.plan", + ondelete="cascade", + check_pms_properties=True, + ) + room_type_id = fields.Many2one( + string="Room Type", + help="Room type for which availability rule is applied", + required=True, + index=True, + comodel_name="pms.room.type", + ondelete="cascade", + check_pms_properties=True, + ) + date = fields.Date( + string="Date", + help="Date for which availability rule applies", + ) + + min_stay = fields.Integer( + string="Min. Stay", + help="Minimum stay", + default=0, + ) + min_stay_arrival = fields.Integer( + string="Min. Stay Arrival", + help="Minimum stay if checkin is today", + default=0, + ) + max_stay = fields.Integer( + string="Max. Stay", + help="Maximum stay", + default=0, + ) + max_stay_arrival = fields.Integer( + string="Max. Stay Arrival", + help="Maximum stay if checkin is today", + default=0, + ) + closed = fields.Boolean( + string="Closed", + help="Indicate if property is closed or not", + default=False, + ) + closed_departure = fields.Boolean( + string="Closed Departure", + help="", + default=False, + ) + closed_arrival = fields.Boolean( + string="Closed Arrival", + help="", + default=False, + ) + quota = fields.Integer( + string="Quota", + help="Generic Quota assigned.", + readonly=False, + store=True, + compute="_compute_quota", + ) + max_avail = fields.Integer( + string="Max. Availability", + help="Maximum simultaneous availability on own Booking Engine", + readonly=False, + store=True, + compute="_compute_max_avail", + ) + pms_property_id = fields.Many2one( + string="Property", + help="Properties with access to the element", + ondelete="restrict", + required=True, + index=True, + comodel_name="pms.property", + check_pms_properties=True, + ) + avail_id = fields.Many2one( + string="Avail record", + comodel_name="pms.availability", + compute="_compute_avail_id", + store=True, + readonly=False, + index=True, + ondelete="restrict", + check_pms_properties=True, + ) + real_avail = fields.Integer( + string="Real availability", + related="avail_id.real_avail", + store="True", + ) + plan_avail = fields.Integer( + compute="_compute_plan_avail", + store="True", + ) + + _sql_constraints = [ + ( + "room_type_registry_unique", + "unique(availability_plan_id, room_type_id, date, pms_property_id)", + "Only can exists one availability rule in the same \ + day for the same room type!", + ) + ] + + @api.depends("room_type_id", "date", "pms_property_id") + def _compute_avail_id(self): + for record in self: + if record.room_type_id and record.pms_property_id and record.date: + avail = self.env["pms.availability"].search( + [ + ("date", "=", record.date), + ("room_type_id", "=", record.room_type_id.id), + ("pms_property_id", "=", record.pms_property_id.id), + ] + ) + if avail: + record.avail_id = avail.id + else: + record.avail_id = self.env["pms.availability"].create( + { + "date": record.date, + "room_type_id": record.room_type_id.id, + "pms_property_id": record.pms_property_id.id, + } + ) + else: + record.avail_id = False + + @api.depends("quota", "max_avail", "real_avail") + def _compute_plan_avail(self): + for record in self: + real_avail = record.real_avail + plan_avail = min( + [ + record.max_avail if record.max_avail >= 0 else real_avail, + record.quota if record.quota >= 0 else real_avail, + real_avail, + ] + ) + if not record.plan_avail or record.plan_avail != plan_avail: + record.plan_avail = plan_avail + + @api.depends("room_type_id") + def _compute_quota(self): + for record in self: + if not record.quota: + record.quota = record.room_type_id.default_quota + + @api.depends("room_type_id") + def _compute_max_avail(self): + for record in self: + if not record.max_avail: + record.max_avail = record.room_type_id.default_max_avail + + @api.constrains("min_stay", "min_stay_arrival", "max_stay", "max_stay_arrival") + def _check_min_max_stay(self): + for record in self: + if record.min_stay < 0: + raise ValidationError(_("Min. Stay can't be less than zero")) + elif record.min_stay_arrival < 0: + raise ValidationError(_("Min. Stay Arrival can't be less than zero")) + elif record.max_stay < 0: + raise ValidationError(_("Max. Stay can't be less than zero")) + elif record.max_stay_arrival < 0: + raise ValidationError(_("Max. Stay Arrival can't be less than zero")) + elif ( + record.min_stay != 0 + and record.max_stay != 0 + and record.min_stay > record.max_stay + ): + raise ValidationError(_("Max. Stay can't be less than Min. Stay")) + elif ( + record.min_stay_arrival != 0 + and record.max_stay_arrival != 0 + and record.min_stay_arrival > record.max_stay_arrival + ): + raise ValidationError( + _("Max. Stay Arrival can't be less than Min. Stay Arrival") + ) diff --git a/pms/models/pms_board_service.py b/pms/models/pms_board_service.py new file mode 100644 index 0000000000..2287dc0cab --- /dev/null +++ b/pms/models/pms_board_service.py @@ -0,0 +1,133 @@ +# Copyright 2017 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class PmsBoardService(models.Model): + _name = "pms.board.service" + _description = "Board Services" + _check_pms_properties_auto = True + + name = fields.Char( + string="Board Service Name", + help="Board Service Name", + required=True, + index=True, + size=64, + translate=True, + ) + default_code = fields.Char( + string="Board Service Code", + help="Unique Board Service identification code per property", + required=True, + ) + board_service_line_ids = fields.One2many( + string="Board Service Lines", + help="Services included in this Board Service", + comodel_name="pms.board.service.line", + inverse_name="pms_board_service_id", + ) + pms_property_ids = fields.Many2many( + string="Properties", + help="Properties with access to the element;" + " if not set, all properties can access", + required=False, + ondelete="restrict", + comodel_name="pms.property", + relation="pms_board_service_pms_property_rel", + column1="board_service_id", + column2="pms_property_id", + check_pms_properties=True, + ) + pms_board_service_room_type_ids = fields.One2many( + string="Board Services Room Type", + help="Board Services Room Type corresponding to this Board Service," + "One board service for several room types", + comodel_name="pms.board.service.room.type", + inverse_name="pms_board_service_id", + ) + amount = fields.Float( + string="Amount", + help="Price for this Board Service. " + "It corresponds to the sum of his board service lines", + store=True, + digits=("Product Price"), + compute="_compute_board_amount", + ) + + show_detail_report = fields.Boolean( + string="Show Detail Report", + help="True if you want that board service detail to be shown on the report", + ) + + @api.depends("board_service_line_ids.amount") + def _compute_board_amount(self): + for record in self: + total = 0 + for service in record.board_service_line_ids: + total += service.amount + record.update({"amount": total}) + + @api.model + def get_unique_by_property_code(self, pms_property_id, default_code=None): + """ + :param pms_property_id: property ID + :param default_code: board service code (optional) + :return: - recordset of + - all the pms.board.service of the pms_property_id + if default_code not defined + - one or 0 pms.board.service if default_code defined + - ValidationError if more than one default_code found by + the same pms_property_id + """ + # TODO: similiar code as room.type -> unify + domain = [] + if default_code: + domain += ["&", ("default_code", "=", default_code)] + domain += [ + "|", + ("pms_property_ids", "in", pms_property_id), + ("pms_property_ids", "=", False), + ] + records = self.search(domain) + res, res_priority = {}, {} + for rec in records: + res_priority.setdefault(rec.default_code, -1) + priority = rec.pms_property_ids and 1 or 0 + if priority > res_priority[rec.default_code]: + res.setdefault(rec.default_code, rec.id) + res[rec.default_code], res_priority[rec.default_code] = rec.id, priority + elif priority == res_priority[rec.default_code]: + raise ValidationError( + _( + "Integrity error: There's multiple board services " + "with the same code %s and properties" + ) + % rec.default_code + ) + return self.browse(list(res.values())) + + @api.constrains("default_code", "pms_property_ids") + def _check_code_property_uniqueness(self): + # TODO: similiar code as room.type -> unify + msg = _( + "Already exists another Board Service with the same code and properties" + ) + for rec in self: + if not rec.pms_property_ids: + if self.search( + [ + ("id", "!=", rec.id), + ("default_code", "=", rec.default_code), + ("pms_property_ids", "=", False), + ] + ): + raise ValidationError(msg) + else: + for pms_property in rec.pms_property_ids: + other = rec.get_unique_by_property_code( + pms_property.id, rec.default_code + ) + if other and other != rec: + raise ValidationError(msg) diff --git a/pms/models/pms_board_service_line.py b/pms/models/pms_board_service_line.py new file mode 100644 index 0000000000..fa130cb8fa --- /dev/null +++ b/pms/models/pms_board_service_line.py @@ -0,0 +1,132 @@ +# Copyright 2017 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class PmsBoardServiceLine(models.Model): + _name = "pms.board.service.line" + _description = "Services on Board Service included" + _check_pms_properties_auto = True + + pms_board_service_id = fields.Many2one( + string="Board Service", + help="Board Service in which this line is included", + required=True, + index=True, + comodel_name="pms.board.service", + ondelete="cascade", + check_pms_properties=True, + ) + product_id = fields.Many2one( + string="Product", + help="Product associated with this board service line", + required=True, + index=True, + comodel_name="product.product", + check_pms_properties=True, + ) + pms_property_ids = fields.Many2many( + string="Properties", + help="Properties with access to the element;" + " if not set, all properties can access", + comodel_name="pms.property", + relation="pms_board_service_line_pms_property_rel", + column1="pms_board_service_line_id", + column2="pms_property_id", + store=True, + check_pms_properties=True, + ) + amount = fields.Float( + string="Amount", + help="Price for this Board Service Line/Product", + default=lambda self: self._get_default_price(), + compute="_compute_amount", + inverse="_inverse_amount", + digits=("Product Price"), + ) + adults = fields.Boolean( + string="Adults", + help="Apply service to adults", + default=False, + ) + children = fields.Boolean( + string="Children", + help="Apply service to children", + default=False, + ) + + def _get_default_price(self): + if self.product_id: + return self.product_id.list_price + + @api.depends_context("allowed_pms_property_ids") + def _compute_amount(self): + for record in self: + pms_property_id = ( + self.env.context.get("property") + or self.env.user.get_active_property_ids()[0] + ) + record.amount = self.env["ir.pms.property"].get_field_value( + pms_property_id, + self._name, + "amount", + record.id, + type(record.amount), + ) + + def _inverse_amount(self): + for record in self: + pms_property_id = ( + self.env.context.get("property") + or self.env.user.get_active_property_ids()[0] + ) + self.env["ir.pms.property"].set_field_value( + pms_property_id, + self._name, + "amount", + record.id, + record.amount, + ) + + @api.onchange("product_id") + def onchange_product_id(self): + if self.product_id: + self.update({"amount": self.product_id.list_price}) + + @api.model + def create(self, vals): + properties = False + if "pms_board_service_id" in vals: + board_service = self.env["pms.board.service"].browse( + vals["pms_board_service_id"] + ) + properties = board_service.pms_property_ids + if properties: + vals.update( + { + "pms_property_ids": properties, + } + ) + return super(PmsBoardServiceLine, self).create(vals) + + def write(self, vals): + properties = False + if "pms_board_service_id" in vals: + board_service = self.env["pms.board.service"].browse( + vals["pms_board_service_id"] + ) + properties = board_service.pms_property_ids + if properties: + vals.update( + { + "pms_property_ids": properties, + } + ) + return super(PmsBoardServiceLine, self).write(vals) + + @api.constrains("adults", "children") + def _check_adults_children(self): + for record in self: + if not record.adults and not record.children: + raise ValidationError(_("Adults or Children must be checked")) diff --git a/pms/models/pms_board_service_room_type.py b/pms/models/pms_board_service_room_type.py new file mode 100644 index 0000000000..95dcc6900d --- /dev/null +++ b/pms/models/pms_board_service_room_type.py @@ -0,0 +1,157 @@ +# Copyright 2017 Dario +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import _, api, fields, models +from odoo.exceptions import UserError + + +class PmsBoardServiceRoomType(models.Model): + _name = "pms.board.service.room.type" + _table = "pms_board_service_room_type_rel" + _rec_name = "pms_board_service_id" + _log_access = False + _description = "Board Service included in Room" + _check_pms_properties_auto = True + + pms_board_service_id = fields.Many2one( + string="Board Service", + help="Board Service corresponding to this Board Service Room Type", + required=True, + index=True, + comodel_name="pms.board.service", + ondelete="cascade", + check_pms_properties=True, + ) + pms_property_id = fields.Many2one( + string="Property", + help="Property with access to the element;" + " if not set, all property can access", + required=False, + index=True, + ondelete="restrict", + comodel_name="pms.property", + check_pms_properties=True, + store=True, + ) + pms_room_type_id = fields.Many2one( + string="Room Type", + help="Room Type for which this Board Service is available", + required=True, + index=True, + comodel_name="pms.room.type", + ondelete="cascade", + check_pms_properties=True, + ) + board_service_line_ids = fields.One2many( + string="Board Service Lines", + help="Services included in this Board Service", + comodel_name="pms.board.service.room.type.line", + inverse_name="pms_board_service_room_type_id", + required=True, + ) + amount = fields.Float( + string="Amount", + help="Price for this Board Service. " + "It corresponds to the sum of his board service lines", + store=True, + digits=("Product Price"), + compute="_compute_board_amount", + ) + by_default = fields.Boolean( + string="Apply by Default", + help="Indicates if this board service is applied by default in the room type", + ) + + @api.depends("board_service_line_ids.amount") + def _compute_board_amount(self): + for record in self: + total = 0 + for service in record.board_service_line_ids: + total += service.amount + record.update({"amount": total}) + + def name_get(self): + res = [] + for record in self: + name = "{} - {}".format( + record.pms_board_service_id.name, record.pms_room_type_id.name + ) + res.append((record.id, name)) + return res + + @api.constrains("by_default") + def constrains_duplicated_board_default(self): + for record in self: + default_boards = ( + record.pms_room_type_id.board_service_room_type_ids.filtered( + "by_default" + ) + ) + # TODO Check properties (with different propertys is allowed) + if any( + default_boards.filtered( + lambda l: l.id != record.id + and l.pms_property_id == record.pms_property_id + ) + ): + raise UserError(_("""Only can set one default board service""")) + + def open_board_lines_form(self): + action = ( + self.env.ref("pms.action_pms_board_service_room_type_view").sudo().read()[0] + ) + action["views"] = [ + (self.env.ref("pms.pms_board_service_room_type_form").id, "form") + ] + action["res_id"] = self.id + action["target"] = "new" + return action + + def init(self): + self._cr.execute( + "SELECT indexname FROM pg_indexes WHERE indexname = %s", + ("pms_board_service_id_pms_room_type_id",), + ) + if not self._cr.fetchone(): + self._cr.execute( + "CREATE INDEX pms_board_service_id_pms_room_type_id \ + ON pms_board_service_room_type_rel \ + (pms_board_service_id, pms_room_type_id)" + ) + + @api.model + def create(self, vals): + # properties = False + if "pms_board_service_id" in vals and "board_service_line_ids" not in vals: + vals.update( + self.prepare_board_service_reservation_ids(vals["pms_board_service_id"]) + ) + return super(PmsBoardServiceRoomType, self).create(vals) + + def write(self, vals): + if "pms_board_service_id" in vals and "board_service_line_ids" not in vals: + vals.update( + self.prepare_board_service_reservation_ids(vals["pms_board_service_id"]) + ) + return super(PmsBoardServiceRoomType, self).write(vals) + + @api.model + def prepare_board_service_reservation_ids(self, board_service_id): + """ + Prepare line to price products config + """ + cmds = [(5, 0, 0)] + board_service = self.env["pms.board.service"].browse(board_service_id) + for line in board_service.board_service_line_ids: + cmds.append( + ( + 0, + False, + { + "product_id": line.product_id.id, + "amount": line.amount, + "adults": line.adults, + "children": line.children, + }, + ) + ) + return {"board_service_line_ids": cmds} diff --git a/pms/models/pms_board_service_room_type_line.py b/pms/models/pms_board_service_room_type_line.py new file mode 100644 index 0000000000..9381a1bea8 --- /dev/null +++ b/pms/models/pms_board_service_room_type_line.py @@ -0,0 +1,62 @@ +# Copyright 2017 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class PmsBoardServiceRoomTypeLine(models.Model): + _name = "pms.board.service.room.type.line" + _description = "Services on Board Service included in Room" + _check_pms_properties_auto = True + + # Fields declaration + pms_board_service_room_type_id = fields.Many2one( + string="Board Service Room", + help="Board Service Room Type in which this line is included", + required=True, + index=True, + comodel_name="pms.board.service.room.type", + ondelete="cascade", + ) + pms_property_id = fields.Many2one( + string="Property", + help="Property with access to the element;" + " if not set, all properties can access", + comodel_name="pms.property", + check_pms_properties=True, + related="pms_board_service_room_type_id.pms_property_id", + ) + product_id = fields.Many2one( + string="Product", + help="Product associated with this board service room type line", + comodel_name="product.product", + index=True, + readonly=True, + check_pms_properties=True, + domain="[('is_pms_available', '=', True)]", + ) + amount = fields.Float( + string="Amount", + help="Price for this Board Service Room Type Line/Product", + default=lambda self: self._default_amount(), + digits=("Product Price"), + ) + adults = fields.Boolean( + string="Adults", + help="Apply service to adults", + default=False, + ) + children = fields.Boolean( + string="Children", + help="Apply service to children", + default=False, + ) + + def _default_amount(self): + return self.product_id.list_price + + @api.constrains("adults", "children") + def _check_adults_children(self): + for record in self: + if not record.adults and not record.children: + raise ValidationError(_("Adults or Children must be checked")) diff --git a/pms/models/pms_cancelation_rule.py b/pms/models/pms_cancelation_rule.py new file mode 100644 index 0000000000..8bd0c53d3f --- /dev/null +++ b/pms/models/pms_cancelation_rule.py @@ -0,0 +1,89 @@ +# 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 + + +class PmsCancelationRule(models.Model): + _name = "pms.cancelation.rule" + _description = "Cancelation Rules" + _check_pms_properties_auto = True + + name = fields.Char( + string="Cancelation Rule", + required=True, + translate=True, + ) + pricelist_ids = fields.One2many( + string="Pricelist", + help="Pricelist that use this rule", + comodel_name="product.pricelist", + inverse_name="cancelation_rule_id", + check_pms_properties=True, + domain="[('is_pms_available', '=', True)]", + ) + pms_property_ids = fields.Many2many( + string="Properties", + help="Properties with access to the element;" + " if not set, all properties can access", + required=False, + comodel_name="pms.property", + relation="pms_cancelation_rule_pms_property_rel", + column1="pms_cancelation_rule_id", + column2="pms_property_id", + ondelete="restrict", + check_pms_properties=True, + ) + active = fields.Boolean( + string="Active", help="Determines if cancelation rule is active", default=True + ) + days_intime = fields.Integer( + string="Days Late", + help="Maximum number of days for free cancellation before Checkin", + ) + penalty_late = fields.Integer( + string="% Penalty Late", + help="Percentage of the total price that partner has " + "to pay in case of late arrival", + default="100", + ) + apply_on_late = fields.Selection( + string="Late apply on", + help="Days on which the cancelation rule applies when " + "the reason is late arrival. " + "Can be first, all days or specify the days.", + default="first", + selection=[ + ("first", "First Day"), + ("all", "All Days"), + ("days", "Specify days"), + ], + ) + days_late = fields.Integer( + string="Late first days", + help="Is number of days late in the cancelation rule " + "if the value of the apply_on_late field is specify days.", + default="2", + ) + penalty_noshow = fields.Integer( + string="% Penalty No Show", + help="Percentage of the total price that partner has to pay in case of no show", + default="100", + ) + apply_on_noshow = fields.Selection( + string="No Show apply on", + help="Days on which the cancelation rule applies when" + " the reason is no show. Can be first, all days or specify the days.", + selection=[ + ("first", "First Day"), + ("all", "All Days"), + ("days", "Specify days"), + ], + default="all", + ) + days_noshow = fields.Integer( + string="NoShow first days", + help="Is number of days no show in the cancelation rule " + "if the value of the apply_on_show field is specify days.", + default="2", + ) diff --git a/pms/models/pms_checkin_partner.py b/pms/models/pms_checkin_partner.py new file mode 100644 index 0000000000..f72e636030 --- /dev/null +++ b/pms/models/pms_checkin_partner.py @@ -0,0 +1,1056 @@ +# Copyright 2017 Dario Lodeiros +# Copyright 2018 Alexandre Diaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import json +from datetime import datetime + +from dateutil.relativedelta import relativedelta + +from odoo import _, api, fields, models +from odoo.exceptions import UserError, ValidationError +from odoo.tools import DEFAULT_SERVER_DATE_FORMAT +from odoo.tools.safe_eval import safe_eval + + +class PmsCheckinPartner(models.Model): + _name = "pms.checkin.partner" + _description = "Partner Checkins" + _inherit = ["mail.thread", "mail.activity.mixin", "portal.mixin"] + _rec_name = "identifier" + + identifier = fields.Char( + string="Identifier", + help="Checkin Partner Id", + readonly=True, + index=True, + default=lambda self: _("New"), + ) + partner_id = fields.Many2one( + string="Partner", + help="Partner associated with checkin partner", + readonly=False, + index=True, + store=True, + comodel_name="res.partner", + domain="[('is_company', '=', False)]", + compute="_compute_partner_id", + ) + reservation_id = fields.Many2one( + string="Reservation", + help="Reservation to which checkin partners belong", + index=True, + comodel_name="pms.reservation", + ) + folio_id = fields.Many2one( + string="Folio", + help="Folio to which reservation of checkin partner belongs", + store=True, + index=True, + comodel_name="pms.folio", + related="reservation_id.folio_id", + ) + pms_property_id = fields.Many2one( + string="Property", + help="Property to which the folio associated belongs", + readonly=True, + store=True, + index=True, + comodel_name="pms.property", + related="reservation_id.pms_property_id", + ) + name = fields.Char( + string="Name", help="Checkin partner name", related="partner_id.name" + ) + email = fields.Char( + string="E-mail", + help="Checkin Partner Email", + readonly=False, + store=True, + compute="_compute_email", + ) + mobile = fields.Char( + string="Mobile", + help="Checkin Partner Mobile", + readonly=False, + store=True, + compute="_compute_mobile", + ) + phone = fields.Char( + string="Phone", + help="Checkin Partner Phone", + readonly=False, + store=True, + compute="_compute_phone", + ) + image_128 = fields.Image( + string="Image", + help="Checkin Partner Image, it corresponds with Partner Image associated", + related="partner_id.image_128", + ) + segmentation_ids = fields.Many2many( + string="Segmentation", + help="Segmentation tags to classify checkin partners", + readonly=True, + related="reservation_id.segmentation_ids", + domain="[('is_used_in_checkin', '=', True)]", + ) + checkin = fields.Date( + string="Checkin", + help="Checkin date", + store=True, + related="reservation_id.checkin", + depends=["reservation_id.checkin"], + ) + checkout = fields.Date( + string="Checkout", + help="Checkout date", + store=True, + related="reservation_id.checkout", + depends=["reservation_id.checkout"], + ) + arrival = fields.Datetime("Enter", help="Checkin partner arrival date and time") + departure = fields.Datetime( + string="Exit", help="Checkin partner departure date and time" + ) + state = fields.Selection( + string="Status", + help="Status of the checkin partner regarding the reservation", + readonly=True, + store=True, + selection=[ + ("dummy", "Unkown Guest"), + ("draft", "Incomplete data"), + ("precheckin", "Pending arrival"), + ("onboard", "On Board"), + ("done", "Out"), + ("cancel", "Cancelled"), + ], + compute="_compute_state", + ) + + gender = fields.Selection( + string="Gender", + help="host gender", + readonly=False, + store=True, + compute="_compute_gender", + selection=[("male", "Male"), ("female", "Female"), ("other", "Other")], + ) + nationality_id = fields.Many2one( + string="Nationality", + help="host nationality", + readonly=False, + store=True, + index=True, + compute="_compute_nationality_id", + comodel_name="res.country", + ) + residence_street = fields.Char( + string="Street", + help="Street of the guest's residence", + readonly=False, + store=True, + compute="_compute_residence_street", + ) + residence_street2 = fields.Char( + string="Street2", + help="Second street of the guest's residence", + readonly=False, + store=True, + compute="_compute_residence_street2", + ) + residence_zip = fields.Char( + string="Zip", + help="Zip of the guest's residence", + readonly=False, + store=True, + compute="_compute_residence_zip", + change_default=True, + ) + residence_city = fields.Char( + string="City", + help="City of the guest's residence", + readonly=False, + store=True, + compute="_compute_residence_city", + ) + residence_country_id = fields.Many2one( + string="Country of residence", + help="Country of the guest's residence", + readonly=False, + store=True, + index=True, + compute="_compute_residence_country_id", + comodel_name="res.country", + ) + residence_state_id = fields.Many2one( + string="State of residence", + help="State of the guest's residence", + readonly=False, + store=True, + index=True, + compute="_compute_residence_state_id", + comodel_name="res.country.state", + ) + + firstname = fields.Char( + string="First Name", + help="host firstname", + readonly=False, + store=True, + compute="_compute_firstname", + ) + lastname = fields.Char( + string="Last Name", + help="host lastname", + readonly=False, + store=True, + compute="_compute_lastname", + ) + lastname2 = fields.Char( + string="Second Last Name", + help="host second lastname", + readonly=False, + store=True, + compute="_compute_lastname2", + ) + birthdate_date = fields.Date( + string="Birthdate", + help="host birthdate", + readonly=False, + store=True, + compute="_compute_birth_date", + ) + document_number = fields.Char( + string="Document Number", + help="Host document number", + readonly=False, + store=True, + compute="_compute_document_number", + ) + document_type = fields.Many2one( + string="Document Type", + help="Select a valid document type", + readonly=False, + store=True, + index=True, + comodel_name="res.partner.id_category", + compute="_compute_document_type", + domain="['|', ('country_ids', '=', False), ('country_ids', 'in', document_country_id)]", + ) + document_expedition_date = fields.Date( + string="Expedition Date", + help="Date on which document_type was issued", + readonly=False, + store=True, + compute="_compute_document_expedition_date", + ) + + document_id = fields.Many2one( + string="Document", + help="Technical field", + readonly=False, + store=True, + index=True, + comodel_name="res.partner.id_number", + compute="_compute_document_id", + ondelete="restrict", + ) + + document_country_id = fields.Many2one( + string="Document Country", + help="Country of the document", + comodel_name="res.country", + compute="_compute_document_country_id", + store=True, + readonly=False, + ) + + partner_incongruences = fields.Char( + string="partner_incongruences", + help="indicates that some partner fields \ + on the checkin do not correspond to that of \ + the associated partner", + compute="_compute_partner_incongruences", + ) + + possible_existing_customer_ids = fields.One2many( + string="Possible existing customer", + compute="_compute_possible_existing_customer_ids", + comodel_name="res.partner", + inverse_name="checkin_partner_possible_customer_id", + ) + + partner_relationship = fields.Char( + string="Partner relationship", help="Family relationship between travelers" + ) + + signature = fields.Image( + string="Signature", + help="Signature of the guest", + ) + + sign_on = fields.Datetime( + string="Sign on", + help="Date and time of the signature", + compute="_compute_sign_on", + ) + + @api.depends("partner_id") + def _compute_document_number(self): + for record in self: + if not record.document_number and record.partner_id.id_numbers: + last_update_document = record.partner_id.id_numbers.filtered( + lambda x: x.write_date + == max(record.partner_id.id_numbers.mapped("write_date")) + ) + if last_update_document and last_update_document[0].name: + record.document_number = last_update_document[0].name + + @api.depends("partner_id") + def _compute_document_type(self): + for record in self: + if not record.document_type and record.partner_id.id_numbers: + last_update_document = record.partner_id.id_numbers.filtered( + lambda x: x.write_date + == max(record.partner_id.id_numbers.mapped("write_date")) + ) + if last_update_document and last_update_document[0].category_id: + record.document_type = last_update_document[0].category_id + + @api.depends("partner_id") + def _compute_document_expedition_date(self): + for record in self: + if not record.document_expedition_date and record.partner_id.id_numbers: + last_update_document = record.partner_id.id_numbers.filtered( + lambda x: x.write_date + == max(record.partner_id.id_numbers.mapped("write_date")) + ) + if last_update_document and last_update_document[0].valid_from: + record.document_expedition_date = last_update_document[0].valid_from + + @api.depends("partner_id") + def _compute_document_country_id(self): + for record in self: + if not record.document_country_id and record.partner_id.id_numbers: + last_update_document = record.partner_id.id_numbers.filtered( + lambda x: x.write_date + == max(record.partner_id.id_numbers.mapped("write_date")) + ) + if last_update_document and last_update_document[0].country_id: + record.document_country_id = last_update_document[0].country_id + + @api.depends("partner_id") + def _compute_firstname(self): + for record in self: + if not record.firstname and record.partner_id.firstname: + record.firstname = record.partner_id.firstname + elif not record.firstname: + record.firstname = False + + @api.depends("partner_id") + def _compute_lastname(self): + for record in self: + if not record.lastname and record.partner_id.lastname: + record.lastname = record.partner_id.lastname + elif not record.lastname: + record.lastname = False + + @api.depends("partner_id") + def _compute_lastname2(self): + for record in self: + if not record.lastname2 and record.partner_id.lastname2: + record.lastname2 = record.partner_id.lastname2 + elif not record.lastname2: + record.lastname2 = False + + @api.depends("partner_id") + def _compute_birth_date(self): + for record in self: + if not record.birthdate_date and record.partner_id.birthdate_date: + record.birthdate_date = record.partner_id.birthdate_date + elif not record.birthdate_date: + record.birthdate_date = False + + @api.depends("partner_id") + def _compute_gender(self): + for record in self: + if not record.gender and record.partner_id.gender: + record.gender = record.partner_id.gender + elif not record.gender: + record.gender = False + + @api.depends("partner_id") + def _compute_nationality_id(self): + for record in self: + if not record.nationality_id and record.partner_id.nationality_id: + record.nationality_id = record.partner_id.nationality_id + elif not record.nationality_id: + record.nationality_id = False + + @api.depends("partner_id") + def _compute_residence_street(self): + for record in self: + if not record.residence_street and record.partner_id.residence_street: + record.residence_street = record.partner_id.residence_street + elif not record.residence_street: + record.residence_street = False + + @api.depends("partner_id") + def _compute_residence_street2(self): + for record in self: + if not record.residence_street2 and record.partner_id.residence_street2: + record.residence_street2 = record.partner_id.residence_street2 + elif not record.residence_street2: + record.residence_street2 = False + + @api.depends("partner_id") + def _compute_residence_zip(self): + for record in self: + if not record.residence_zip and record.partner_id.residence_zip: + record.residence_zip = record.partner_id.residence_zip + elif not record.residence_zip: + record.residence_zip = False + + @api.depends("partner_id") + def _compute_residence_city(self): + for record in self: + if not record.residence_city and record.partner_id.residence_city: + record.residence_city = record.partner_id.residence_city + elif not record.residence_city: + record.residence_city = False + + @api.depends("partner_id", "nationality_id") + def _compute_residence_country_id(self): + for record in self: + if ( + not record.residence_country_id + and record.partner_id.residence_country_id + ): + record.residence_country_id = record.partner_id.residence_country_id + + @api.depends("partner_id") + def _compute_residence_state_id(self): + for record in self: + if not record.residence_state_id and record.partner_id.residence_state_id: + record.residence_state_id = record.partner_id.residence_state_id + elif not record.residence_state_id: + record.residence_state_id = False + + @api.depends(lambda self: self._checkin_manual_fields(depends=True)) + def _compute_state(self): + for record in self: + if not record.state: + record.state = "dummy" + if record.reservation_id.state == "cancel": + record.state = "cancel" + elif record.state in ("dummy", "draft", "precheckin", "cancel"): + if all( + not getattr(record, field) + for field in record._checkin_manual_fields() + ): + record.state = "dummy" + elif any( + not getattr(record, field) + for field in record._checkin_mandatory_fields( + country=record.document_country_id + ) + ): + record.state = "draft" + else: + record.state = "precheckin" + + @api.depends("partner_id") + def _compute_name(self): + for record in self: + if not record.name or record.partner_id.name: + record.name = record.partner_id.name + + @api.depends("partner_id") + def _compute_email(self): + for record in self: + if not record.email and record.partner_id.email: + record.email = record.partner_id.email + elif not record.email: + record.email = False + + @api.depends("partner_id") + def _compute_mobile(self): + for record in self: + if not record.mobile and record.partner_id.mobile: + record.mobile = record.partner_id.mobile + elif not record.mobile: + record.mobile = False + + @api.depends("partner_id") + def _compute_phone(self): + for record in self: + if not record.phone and record.partner_id.phone: + record.phone = record.partner_id.phone + elif not record.phone: + record.phone = False + + @api.depends("partner_id") + def _compute_document_id(self): + for record in self: + if record.partner_id: + if ( + not record.document_id + and record.document_number + and record.document_type + ): + id_number_id = ( + self.sudo() + .env["res.partner.id_number"] + .search( + [ + ("partner_id", "=", record.partner_id.id), + ("name", "=", record.document_number), + ("category_id", "=", record.document_type.id), + ] + ) + ) + if not id_number_id: + id_number_id = self.env["res.partner.id_number"].create( + { + "partner_id": record.partner_id.id, + "name": record.document_number, + "category_id": record.document_type.id, + "valid_from": record.document_expedition_date, + } + ) + + record.document_id = id_number_id + else: + record.document_id = False + + @api.depends( + "document_number", + "document_type", + "firstname", + "lastname", + "lastname2", + ) + def _compute_partner_id(self): + for record in self: + if not record.partner_id: + if record.document_number and record.document_type: + partner = self._get_partner_by_document( + record.document_number, record.document_type + ) + if not partner: + if record.firstname or record.lastname or record.lastname2: + partner_values = { + "firstname": record.firstname, + "lastname": record.lastname, + "lastname2": record.lastname2, + "gender": record.gender, + "birthdate_date": record.birthdate_date, + "nationality_id": record.nationality_id.id, + } + partner = ( + self.env["res.partner"] + .with_context(avoid_document_restriction=True) + .create(partner_values) + ) + record.partner_id = partner + + @api.model + def _get_partner_by_document(self, document_number, document_type): + number = ( + self.sudo() + .env["res.partner.id_number"] + .search( + [ + ("name", "=", document_number), + ("category_id", "=", document_type.id), + ] + ) + ) + return ( + self.sudo().env["res.partner"].search([("id", "=", number.partner_id.id)]) + ) + + @api.depends("email", "mobile") + def _compute_possible_existing_customer_ids(self): + for record in self: + possible_customer = self.env[ + "pms.folio" + ]._apply_possible_existing_customer_ids(record.email, record.mobile) + if possible_customer: + record.possible_existing_customer_ids = possible_customer + else: + record.possible_existing_customer_ids = False + + @api.depends( + "firstname", + "lastname", + "lastname2", + "gender", + "birthdate_date", + "nationality_id", + "email", + "mobile", + "partner_id", + ) + def _compute_partner_incongruences(self): + for record in self: + incongruous_fields = False + if record.partner_id: + for field in record._checkin_partner_fields(): + if ( + record.partner_id[field] + and record.partner_id[field] != record[field] + ): + if not incongruous_fields: + incongruous_fields = record._fields[field].string + else: + incongruous_fields += ", " + record._fields[field].string + if incongruous_fields: + record.partner_incongruences = ( + incongruous_fields + " field/s don't correspond to saved host" + ) + else: + record.partner_incongruences = False + else: + record.partner_incongruences = False + + @api.depends("signature") + def _compute_sign_on(self): + for record in self: + if record.signature: + record.sign_on = datetime.now() + else: + record.sign_on = False + + def _compute_access_url(self): + super(PmsCheckinPartner, self)._compute_access_url() + for checkin in self: + checkin.access_url = "/my/folios/%s/reservations/%s/checkins/%s" % ( + checkin.folio_id.id, + checkin.reservation_id.id, + checkin.id, + ) + + # Constraints and onchanges + + @api.constrains("departure", "arrival") + def _check_departure(self): + for record in self: + if record.departure and record.arrival > record.departure: + raise ValidationError( + _("Departure date (%s) is prior to arrival on %s") + % (record.departure, record.arrival) + ) + + @api.constrains("partner_id") + def _check_partner_id(self): + for record in self: + if record.partner_id: + indoor_partner_ids = record.reservation_id.checkin_partner_ids.filtered( + lambda r: r.id != record.id + ).mapped("partner_id.id") + if indoor_partner_ids.count(record.partner_id.id) > 1: + record.partner_id = None + raise ValidationError( + _("This guest is already registered in the room") + ) + + # REVIEW: Redesign email & mobile control (res.partner? other module in OCA?) + # @api.constrains("email") + # def check_email_pattern(self): + # for record in self: + # if record.email: + # if not re.search( + # r"^[a-zA-Z0-9]([a-zA-z0-9\-\_]*[\.]?[a-zA-Z0-9\-\_]+)*" + # r"@([a-zA-z0-9\-]+([\.][a-zA-Z0-9\-\_]+)?\.[a-zA-Z0-9]+)+$", + # record.email, + # ): + # raise ValidationError(_("'%s' is not a valid email", record.email)) + + # @api.constrains("mobile") + # def check_phone_pattern(self): + + # for record in self: + # if record.mobile: + + # if not re.search( + # r"^(\d{3}[\-\s]?\d{2}[\-\s]?\d{2}[\-\s]?\d{2}[\-\s]?|" + # r"\d{3}[\-\s]?\d{3}[\-\s]?\d{3})$", + # str(record.mobile), + # ): + # raise ValidationError(_("'%s' is not a valid phone", record.mobile)) + + @api.constrains("document_number") + def check_document_number(self): + for record in self: + if record.partner_id: + for number in record.partner_id.id_numbers: + if record.document_type == number.category_id: + if record.document_number != number.name: + raise ValidationError(_("Document_type has already exists")) + + def _validation_eval_context(self, id_number): + self.ensure_one() + return {"self": self, "id_number": id_number} + + @api.constrains("document_number", "document_type") + def validate_id_number(self): + """Validate the given ID number + The method raises an odoo.exceptions.ValidationError if the eval of + python validation code fails + """ + for record in self: + if record.document_number and record.document_type: + id_number = self.env["res.partner.id_number"].new( + { + "name": record.document_number, + "category_id": record.document_type, + } + ) + if ( + self.env.context.get("id_no_validate") + or not record.document_type.validation_code + ): + return + eval_context = record._validation_eval_context(id_number) + try: + safe_eval( + record.document_type.validation_code, + eval_context, + mode="exec", + nocopy=True, + ) + except Exception as e: + raise UserError( + _( + "Error when evaluating the id_category validation code:" + ":\n %s \n(%s)" + ) + % (self.name, e) + ) + if eval_context.get("failed", False): + raise ValidationError( + _("%s is not a valid %s identifier") + % (record.document_number, record.document_type.name) + ) + + @api.constrains("document_country_id", "document_type") + def _check_document_country_id_document_type_consistence(self): + for record in self: + if record.document_country_id and record.document_type: + if ( + record.document_type.country_ids + and record.document_country_id + not in record.document_type.country_ids + ): + raise ValidationError( + _("Document type and country of document do not match") + ) + + @api.model + def create(self, vals): + # The checkin records are created automatically from adult depends + # if you try to create one manually, we update one unassigned checkin + reservation_id = vals.get("reservation_id") + if reservation_id: + reservation = self.env["pms.reservation"].browse(reservation_id) + else: + raise ValidationError( + _("Is mandatory indicate the reservation on the checkin") + ) + # If a checkin is manually created, we need make sure that + # the reservation adults are computed + if not reservation.checkin_partner_ids: + reservation.flush() + dummy_checkins = reservation.checkin_partner_ids.filtered( + lambda c: c.state == "dummy" + ) + if len(reservation.checkin_partner_ids) < reservation.adults: + return super(PmsCheckinPartner, self).create(vals) + if len(dummy_checkins) > 0: + dummy_checkins[0].write(vals) + return dummy_checkins[0] + raise ValidationError( + _("Is not possible to create the proposed check-in in this reservation") + ) + + def unlink(self): + reservations = self.mapped("reservation_id") + res = super().unlink() + reservations._compute_checkin_partner_ids() + return res + + @api.model + def _checkin_manual_fields(self, country=False, depends=False): + manual_fields = [ + "name", + "partner_id", + "email", + "mobile", + "phone", + "gender", + "firstname", + "lastname", + "lastname2", + "birthdate_date", + "document_number", + "document_expedition_date", + "nationality_id", + "residence_street", + "residence_street2", + "residence_zip", + "residence_city", + "residence_country_id", + "residence_state_id", + ] + # api.depends need "reservation_id.state" in the lambda function + if depends: + manual_fields.append("reservation_id.state") + return manual_fields + + @api.model + def _checkin_mandatory_fields(self, country=False, depends=False): + mandatory_fields = [ + "name", + ] + # api.depends need "reservation_id.state" in the lambda function + if depends: + mandatory_fields.extend(["reservation_id.state", "name"]) + + return mandatory_fields + + @api.model + def _checkin_partner_fields(self): + checkin_fields = [ + "firstname", + "lastname", + "lastname2", + "mobile", + "email", + "gender", + "nationality_id", + "birthdate_date", + ] + return checkin_fields + + @api.model + def import_room_list_json(self, roomlist_json): + roomlist_json = json.loads(roomlist_json) + for checkin_dict in roomlist_json: + identifier = checkin_dict["identifier"] + reservation_id = checkin_dict["reservation_id"] + checkin = ( + self.sudo() + .env["pms.checkin.partner"] + .search([("identifier", "=", identifier)]) + ) + reservation = self.env["pms.reservation"].browse(reservation_id) + if not checkin: + raise ValidationError( + _("%s not found in checkins (%s)"), identifier, reservation.name + ) + checkin_vals = {} + for key, value in checkin_dict.items(): + if key in ("reservation_id", "folio_id", "identifier"): + continue + checkin_vals[key] = value + checkin.write(checkin_vals) + + @api.model + def calculate_doc_type_expedition_date_from_validity_date( + self, doc_type, doc_date, birthdate + ): + today = fields.datetime.today() + datetime_doc_date = datetime.strptime(doc_date, DEFAULT_SERVER_DATE_FORMAT) + if datetime_doc_date < today: + return datetime_doc_date + datetime_birthdate = datetime.strptime(birthdate, DEFAULT_SERVER_DATE_FORMAT) + age = today.year - datetime_birthdate.year + + document_expedition_date = False + if doc_type.code == "D" or doc_type.code == "P": + if age < 30: + document_expedition_date = datetime_doc_date - relativedelta(years=5) + else: + document_expedition_date = datetime_doc_date - relativedelta(years=10) + if doc_type.code == "C": + if age < 70: + document_expedition_date = datetime_doc_date - relativedelta(years=10) + return document_expedition_date + + def action_on_board(self): + for record in self: + if record.reservation_id.checkin > fields.Date.today(): + raise ValidationError(_("It is not yet checkin day!")) + if record.reservation_id.checkout < fields.Date.today(): + raise ValidationError(_("Its too late to checkin")) + + if any( + not getattr(record, field) for field in self._checkin_mandatory_fields() + ): + raise ValidationError(_("Personal data is missing for check-in")) + vals = { + "state": "onboard", + "arrival": fields.Datetime.now(), + } + record.update(vals) + record.reservation_id.state = "onboard" + record.identifier = ( + record.reservation_id.pms_property_id.checkin_sequence_id._next_do() + ) + + def action_done(self): + for record in self.filtered(lambda c: c.state == "onboard"): + vals = { + "state": "done", + "departure": fields.Datetime.now(), + } + record.update(vals) + return True + + def action_undo_onboard(self): + for record in self.filtered(lambda c: c.state == "onboard"): + vals = { + "state": "precheckin", + "arrival": False, + } + record.update(vals) + return True + + def open_partner(self): + """Utility method used to add an "View Customer" button in checkin partner views""" + self.ensure_one() + partner_form_id = self.env.ref("pms.view_partner_data_form").id + return { + "type": "ir.actions.act_window", + "res_model": "res.partner", + "view_mode": "form", + "views": [(partner_form_id, "form")], + "res_id": self.partner_id.id, + "target": "new", + "flags": {"form": {"action_buttons": True}}, + } + + def open_wizard_several_partners(self): + ctx = dict( + checkin_partner_id=self.id, + possible_existing_customer_ids=self.possible_existing_customer_ids.ids, + ) + return { + "view_type": "form", + "view_mode": "form", + "name": "Several Customers", + "res_model": "pms.several.partners.wizard", + "target": "new", + "type": "ir.actions.act_window", + "context": ctx, + } + + def _save_data_from_portal(self, values): + checkin_partner = values.get("checkin_partner", "") + values.pop("checkin_partner") + values.pop("folio_access_token") if "folio_access_token" in values else None + if values.get("nationality"): + values.update({"nationality_id": int(values.get("nationality_id"))}) + + doc_type = ( + self.sudo() + .env["res.partner.id_category"] + .browse(int(values.get("document_type"))) + ) + if values.get("document_type"): + values.update({"document_type": int(values.get("document_type"))}) + if values.get("residence_state_id"): + values.update({"residence_state_id": int(values.get("residence_state_id"))}) + if values.get("residence_country_id"): + values.update( + {"residence_country_id": int(values.get("residence_country_id"))} + ) + + if values.get("document_expedition_date"): + values.update( + { + "document_expedition_date": datetime.strptime( + values.get("document_expedition_date"), "%d/%m/%Y" + ).strftime("%Y-%m-%d"), + "birthdate_date": datetime.strptime( + values.get("birthdate_date"), "%d/%m/%Y" + ).strftime("%Y-%m-%d"), + } + ) + doc_date = values.get("document_expedition_date") + birthdate = values.get("birthdate_date") + document_expedition_date = ( + self.calculate_doc_type_expedition_date_from_validity_date( + doc_type, doc_date, birthdate + ) + ) + values.update( + { + "document_expedition_date": document_expedition_date, + } + ) + checkin_partner.sudo().write(values) + + def send_portal_invitation_email(self, invitation_firstname=None, email=None): + template = self.sudo().env.ref( + "pms.precheckin_invitation_email", raise_if_not_found=False + ) + subject = template._render_field( + "subject", [6, 0, self.id], compute_lang=True, post_process=True + )[self.id] + body = template._render_field( + "body_html", [6, 0, self.id], compute_lang=True, post_process=True + )[self.id] + invitation_mail = ( + self.env["mail.mail"] + .sudo() + .create( + { + "subject": subject, + "body_html": body, + "email_from": self.pms_property_id.partner_id.email, + "email_to": email, + } + ) + ) + + invitation_mail.send() + + def send_exit_email(self, template_id): + template = self.env["mail.template"].browse(template_id) + if self.email: + template.send_mail( + self.id, + force_send=True, + raise_exception=False, + email_values={"email_to": self.email, "auto_delete": False}, + ) + body = template._render_field( + "body_html", [6, 0, self.id], compute_lang=True, post_process=True + )[self.id] + self.reservation_id.message_post(body=body) + + if self.reservation_id.to_send_exit_mail: + emails = self.reservation_id.checkin_partner_ids.mapped("email") + if ( + self.reservation_id.partner_id + and self.reservation_id.partner_id.email + and self.reservation_id.partner_id.email not in emails + ): + template.send_mail( + self.partner_id.id, + force_send=True, + raise_exception=False, + email_values={ + "email_to": self.reservation_id.email, + "auto_delete": False, + }, + ) + body = template._render_field( + "body_html", [6, 0, self.id], compute_lang=True, post_process=True + )[self.id] + self.reservation_id.message_post(body=body) + self.reservation_id.to_send_exit_mail = False diff --git a/pms/models/pms_folio.py b/pms/models/pms_folio.py new file mode 100644 index 0000000000..253daf8ec5 --- /dev/null +++ b/pms/models/pms_folio.py @@ -0,0 +1,2782 @@ +# Copyright 2017-2018 Alexandre Díaz +# Copyright 2017 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import datetime +import logging +from itertools import groupby + +from odoo import _, api, fields, models +from odoo.exceptions import AccessError, UserError, ValidationError +from odoo.tools import float_compare, float_is_zero +from odoo.tools.misc import get_lang + +_logger = logging.getLogger(__name__) + + +class PmsFolio(models.Model): + _name = "pms.folio" + _description = "PMS Folio" + _inherit = ["mail.thread", "mail.activity.mixin", "portal.mixin"] + _order = "date_order" + _check_company_auto = True + _check_pms_properties_auto = True + + name = fields.Char( + string="Folio Number", + help="Folio name. When creating a folio the " + "name is automatically formed with a sequence", + readonly=True, + index=True, + default=lambda self: _("New"), + ) + external_reference = fields.Char( + string="External Reference", + help="Reference of this folio in an external system", + compute="_compute_external_reference", + readonly=False, + store=True, + ) + pms_property_id = fields.Many2one( + string="Property", + help="The property for folios", + comodel_name="pms.property", + required=True, + index=True, + default=lambda self: self.env.user.get_active_property_ids()[0], + check_pms_properties=True, + ) + partner_id = fields.Many2one( + string="Partner", + help="The folio customer", + readonly=False, + store=True, + tracking=True, + index=True, + compute="_compute_partner_id", + comodel_name="res.partner", + ondelete="restrict", + check_pms_properties=True, + ) + reservation_ids = fields.One2many( + string="Reservations", + help="Room reservation detail", + readonly=False, + states={"done": [("readonly", True)]}, + comodel_name="pms.reservation", + inverse_name="folio_id", + check_company=True, + check_pms_properties=True, + ) + number_of_rooms = fields.Integer( + string="Number of Rooms", + help="Number of rooms in folio. Canceled rooms do not count.", + store="True", + compute="_compute_number_of_rooms", + ) + number_of_cancelled_rooms = fields.Integer( + string="Number of Cancelled Rooms", + help="Number of cancelled rooms in folio.", + store="True", + compute="_compute_number_of_cancelled_rooms", + ) + number_of_services = fields.Integer( + string="Number of Services", + help="Number of services in the folio", + store="True", + compute="_compute_number_of_services", + ) + service_ids = fields.One2many( + string="Service", + help="Services detail provide to customer and it will " + "include in main Invoice.", + readonly=False, + states={"done": [("readonly", True)]}, + comodel_name="pms.service", + inverse_name="folio_id", + check_company=True, + check_pms_properties=True, + ) + sale_line_ids = fields.One2many( + string="Sale lines", + help="Sale lines in folio. It correspond with reservation nights", + store="True", + compute="_compute_sale_line_ids", + compute_sudo=True, + comodel_name="folio.sale.line", + inverse_name="folio_id", + ) + invoice_count = fields.Integer( + string="Invoice Count", + help="The amount of invoices in out invoice and out refund status", + readonly=True, + compute="_compute_get_invoiced", + ) + company_id = fields.Many2one( + string="Company", + help="The company for folio", + store=True, + index=True, + comodel_name="res.company", + compute="_compute_company_id", + ) + analytic_account_id = fields.Many2one( + string="Analytic Account", + help="The analytic account related to a folio.", + readonly=True, + index=True, + states={"draft": [("readonly", False)], "sent": [("readonly", False)]}, + copy=False, + comodel_name="account.analytic.account", + ) + currency_id = fields.Many2one( + string="Currency", + help="The currency of the property location", + readonly=True, + required=True, + index=True, + related="pricelist_id.currency_id", + ondelete="restrict", + ) + pricelist_id = fields.Many2one( + string="Pricelist", + help="Pricelist for current folio.", + readonly=False, + store=True, + index=True, + comodel_name="product.pricelist", + ondelete="restrict", + check_pms_properties=True, + compute="_compute_pricelist_id", + ) + commission = fields.Float( + string="Commission", + readonly=True, + store=True, + compute="_compute_commission", + ) + user_id = fields.Many2one( + string="Reception Manager", + help="The reception manager in the folio", + readonly=False, + index=True, + store=True, + comodel_name="res.users", + ondelete="restrict", + compute="_compute_user_id", + tracking=True, + ) + revenue_user_id = fields.Many2one( + string="Revenue Manager", + help="The revenue manager in the folio", + readonly=False, + index=True, + store=True, + comodel_name="res.users", + ondelete="restrict", + compute="_compute_revenue_user_id", + tracking=True, + ) + administrative_user_id = fields.Many2one( + string="Administrative Manager", + help="The administrative manager in the folio", + readonly=False, + index=True, + store=True, + comodel_name="res.users", + ondelete="restrict", + compute="_compute_administrative_user_id", + tracking=True, + ) + manager_user_id = fields.Many2one( + string="Main Manager", + help="The main manager in the folio", + readonly=False, + index=True, + store=True, + comodel_name="res.users", + ondelete="restrict", + compute="_compute_manager_user_id", + tracking=True, + ) + agency_id = fields.Many2one( + string="Agency", + help="Only allowed if the field of partner is_agency is True", + comodel_name="res.partner", + domain=[("is_agency", "=", True)], + ondelete="restrict", + index=True, + check_pms_properties=True, + ) + sale_channel_ids = fields.Many2many( + string="Sale Channels", + help="Sale Channels through which reservations were managed", + store=True, + compute="_compute_sale_channel_ids", + comodel_name="pms.sale.channel", + ) + sale_channel_origin_id = fields.Many2one( + string="Sale Channel Origin", + help="Sale Channel through which folio was created, the original", + comodel_name="pms.sale.channel", + index=True, + ) + + transaction_ids = fields.Many2many( + string="Transactions", + help="Payments made through payment acquirer", + readonly=True, + copy=False, + comodel_name="payment.transaction", + relation="payment_transaction_folio_rel", + column1="folio_id", + column2="payment_transaction_id", + ) + payment_ids = fields.Many2many( + string="Bank Payments", + help="Payments", + readonly=True, + copy=False, + comodel_name="account.payment", + relation="account_payment_folio_rel", + column1="folio_id", + column2="payment_id", + ) + statement_line_ids = fields.Many2many( + string="Cash Payments", + help="Statement lines", + readonly=True, + copy=False, + comodel_name="account.bank.statement.line", + relation="account_bank_statement_folio_rel", + column1="folio_id", + column2="account_journal_id", + ) + payment_term_id = fields.Many2one( + string="Payment Terms", + help="Payment terms for current folio.", + readonly=False, + store=True, + index=True, + comodel_name="account.payment.term", + ondelete="restrict", + compute="_compute_payment_term_id", + ) + checkin_partner_ids = fields.One2many( + string="Checkin Partners", + help="The checkin partners on a folio", + comodel_name="pms.checkin.partner", + inverse_name="folio_id", + ) + count_rooms_pending_arrival = fields.Integer( + string="Pending Arrival", + help="The number of rooms left to occupy.", + store=True, + compute="_compute_count_rooms_pending_arrival", + ) + pending_checkin_data = fields.Integer( + string="Checkin Data", + compute="_compute_pending_checkin_data", + store=True, + ) + ratio_checkin_data = fields.Integer( + string="Pending Checkin Data", + help="Field that stores the number of checkin partners pending " + "to checkin (with the state = draft)", + compute="_compute_ratio_checkin_data", + ) + move_ids = fields.Many2many( + string="Invoices", + help="Folio invoices related to account move.", + readonly=True, + copy=False, + comodel_name="account.move", + compute="_compute_get_invoiced", + search="_search_invoice_ids", + ) + untaxed_amount_to_invoice = fields.Monetary( + string="Amount to invoice", + help="The amount to invoice", + readonly=True, + store=True, + compute="_compute_untaxed_amount_to_invoice", + ) + payment_state = fields.Selection( + string="Payment Status", + help="The state of the payment", + copy=False, + readonly=True, + store=True, + selection=[ + ("not_paid", "Not Paid"), + ("paid", "Paid"), + ("partial", "Partially Paid"), + ("overpayment", "Overpayment"), + ("nothing_to_pay", "Nothing to pay"), + ], + compute="_compute_amount", + tracking=True, + ) + partner_invoice_ids = fields.Many2many( + string="Billing addresses", + help="Invoice address for current group.", + readonly=False, + store=True, + comodel_name="res.partner", + relation="pms_folio_partner_rel", + column1="folio", + column2="partner", + compute="_compute_partner_invoice_ids", + check_pms_properties=True, + ) + # REVIEW THIS + # partner_invoice_state_id = fields.Many2one(related="partner_invoice_id.state_id") + # partner_invoice_country_id = fields.Many2one( + # related="partner_invoice_id.country_id" + # ) + fiscal_position_id = fields.Many2one( + string="Fiscal Position", + help="The fiscal position depends on the location of the client", + comodel_name="account.fiscal.position", + index=True, + ) + closure_reason_id = fields.Many2one( + string="Closure Reason", + help="The closure reason for a closure room", + comodel_name="room.closure.reason", + index=True, + check_pms_properties=True, + ) + out_service_description = fields.Text( + string="Cause of out of service", + help="Indicates the cause of out of service", + ) + segmentation_ids = fields.Many2many( + string="Segmentation", + help="Segmentation tags to classify folios", + comodel_name="res.partner.category", + ondelete="restrict", + domain="[('is_used_in_checkin', '=', True)]", + ) + reservation_type = fields.Selection( + string="Type", + help="The type of the reservation. " + "Can be 'Normal', 'Staff' or 'Out of Service'", + default=lambda *a: "normal", + selection=[("normal", "Normal"), ("staff", "Staff"), ("out", "Out of Service")], + ) + date_order = fields.Datetime( + string="Order Date", + help="Date on which folio is sold", + readonly=True, + required=True, + index=True, + default=fields.Datetime.now, + states={"draft": [("readonly", False)], "sent": [("readonly", False)]}, + copy=False, + ) + confirmation_date = fields.Datetime( + string="Confirmation Date", + help="Date on which the folio is confirmed.", + readonly=True, + index=True, + copy=False, + ) + state = fields.Selection( + string="Status", + help="Folio status; it can be Quotation, " + "Quotation Sent, Confirmed, Locked or Cancelled", + readonly=True, + index=True, + default="draft", + copy=False, + selection=[ + ("draft", "Quotation"), + ("sent", "Quotation Sent"), + ("confirm", "Confirmed"), + ("done", "Locked"), + ("cancel", "Cancelled"), + ], + tracking=True, + ) + partner_name = fields.Char( + string="Customer Name", + help="In the name of whom the reservation is made", + store=True, + readonly=False, + compute="_compute_partner_name", + ) + email = fields.Char( + string="E-mail", + help="Customer E-mail", + store=True, + readonly=False, + compute="_compute_email", + ) + mobile = fields.Char( + string="Mobile", + help="Customer Mobile", + store=True, + readonly=False, + compute="_compute_mobile", + ) + partner_incongruences = fields.Char( + string="partner_incongruences", + help="indicates that some partner fields \ + on the folio do not correspond to that of \ + the associated partner", + compute="_compute_partner_incongruences", + ) + partner_internal_comment = fields.Text( + string="Internal Partner Notes", + help="Internal notes of the partner", + related="partner_id.comment", + store=True, + readonly=False, + ) + credit_card_details = fields.Text( + string="Credit Card Details", + help="Details of partner credit card", + ) + + pending_amount = fields.Monetary( + string="Pending Amount", + help="The amount that remains to be paid", + store=True, + compute="_compute_amount", + ) + # refund_amount = fields.Monetary( + # compute="_compute_amount", store=True, string="Payment Returns" + # ) + invoices_paid = fields.Monetary( + string="Paid Out", + help="Amount of invoices paid", + store=True, + compute="_compute_amount", + tracking=True, + ) + payment_multi = fields.Boolean( + string="Folio paid with payments assigned to other folios", + help="Technical field for manage payments with multiple folios assigned", + readonly=True, + store=True, + compute="_compute_amount", + ) + amount_untaxed = fields.Monetary( + string="Untaxed Amount", + help="The price without taxes on a folio", + readonly=True, + store=True, + compute="_compute_amount_all", + tracking=True, + ) + amount_tax = fields.Monetary( + string="Taxes", + help="Price with taxes on a folio", + readonly=True, + store=True, + compute="_compute_amount_all", + ) + amount_total = fields.Monetary( + string="Total", + help="Total amount to be paid", + readonly=True, + store=True, + compute="_compute_amount_all", + tracking=True, + ) + max_reservation_priority = fields.Integer( + string="Max reservation priority on the entire folio", + help="Max reservation priority on the entire folio", + compute="_compute_max_reservation_priority", + store=True, + ) + invoice_status = fields.Selection( + string="Invoice Status", + help="Invoice Status; it can be: invoiced, to invoice, to confirm, no", + readonly=True, + default="no", + store=True, + selection=[ + ("invoiced", "Fully Invoiced"), + ("to_invoice", "To Invoice"), + ("to_confirm", "To Confirm"), + ("no", "Nothing to Invoice"), + ], + compute="_compute_get_invoice_status", + compute_sudo=True, + ) + force_nothing_to_invoice = fields.Boolean( + string="Force no invoice", + help="When you set this field, the folio will be considered as " + "nothin to invoice, even when there may be ordered " + "quantities pending to invoice.", + copy=False, + compute="_compute_force_nothing_to_invoice", + readonly=False, + store=True, + ) + internal_comment = fields.Text( + string="Internal Folio Notes", + help="Internal Folio notes for Staff", + ) + cancelled_reason = fields.Text( + string="Cause of cancelled", + help="Indicates cause of cancelled", + ) + prepaid_warning_days = fields.Integer( + string="Prepaid Warning Days", + help="Margin in days to create a notice if a payment \ + advance has not been recorded", + ) + sequence = fields.Integer( + string="Sequence", + help="Sequence used to form the name of the folio", + default=10, + ) + note = fields.Text( + string="Terms and conditions", + help="Folio billing terms and conditions", + default=lambda self: self._default_note(), + ) + reference = fields.Char( + string="Payment Ref.", + help="The payment communication of this sale order.", + copy=False, + ) + + possible_existing_customer_ids = fields.One2many( + string="Possible existing customer", + compute="_compute_possible_existing_customer_ids", + comodel_name="res.partner", + inverse_name="folio_possible_customer_id", + ) + first_checkin = fields.Date( + string="First Folio Checkin", + readonly=False, + store=True, + compute="_compute_first_checkin", + ) + days_to_checkin = fields.Integer( + string="Days to Checkin", + help="""Technical field to facilitate + filtering by dates related to checkin""", + compute="_compute_days_to_checkin", + search="_search_days_to_checkin", + ) + last_checkout = fields.Date( + string="Last Folio Checkout", + readonly=False, + store=True, + compute="_compute_last_checkout", + ) + days_to_checkout = fields.Integer( + string="Days to Checkout", + help="""Technical field to facilitate + filtering by dates related to checkout""", + compute="_compute_days_to_checkout", + search="_search_days_to_checkout", + ) + invoice_to_agency = fields.Boolean( + string="Invoice Agency", + help="""Indicates if agency invoices partner + (it only affects those nights/services sold through the agency)""", + compute="_compute_invoice_to_agengy", + store=True, + readonly=False, + ) + lang = fields.Selection( + selection=lambda self: self._get_lang_selection_options(), + string="Language", + help="Language used for the folio", + compute="_compute_lang", + store=True, + readonly=False, + ) + + @api.model + def _get_lang_selection_options(self): + """Gets the available languages for the selection.""" + langs = self.env["res.lang"].search([]) + return [(lang.code, lang.name) for lang in langs] + + def name_get(self): + result = [] + for folio in self: + name = folio.name + if len(folio.reservation_ids) > 1: + name += " (%s)" % len(folio.reservation_ids) + result.append((folio.id, name)) + return result + + def _default_note(self): + return ( + self.env["ir.config_parameter"] + .sudo() + .get_param("account.use_invoice_terms") + and self.env.company.invoice_terms + or "" + ) + + def _get_report_base_filename(self): + self.ensure_one() + return "Folio %s" % self.name + + def _get_invoice_grouping_keys(self): + return ["company_id", "partner_id", "currency_id"] + + def get_invoice_vals_list( + self, final=False, lines_to_invoice=False, partner_invoice_id=False + ): + precision = self.env["decimal.precision"].precision_get( + "Product Unit of Measure" + ) + invoice_vals_list = [] + invoice_item_sequence = 0 + for folio in self: + folio_lines_to_invoice = folio.sale_line_ids.filtered( + lambda l: l.id in list(lines_to_invoice.keys()) + ) + groups_invoice_lines = folio._get_groups_invoice_lines( + lines_to_invoice=folio_lines_to_invoice, + partner_invoice_id=partner_invoice_id, + ) + for group in groups_invoice_lines: + folio = folio.with_company(folio.company_id) + down_payments = folio.env["folio.sale.line"] + + # Invoice values. + invoice_vals = folio._prepare_invoice( + partner_invoice_id=group["partner_id"] + ) + # Invoice line values (keep only necessary sections). + current_section_vals = None + invoice_lines_vals = [] + for line in group["lines"]: + if line.display_type == "line_section": + current_section_vals = line._prepare_invoice_line( + sequence=invoice_item_sequence + + folio.sale_line_ids.ids.index(line.id) + ) + continue + if line.display_type != "line_note" and float_is_zero( + line.qty_to_invoice, precision_digits=precision + ): + continue + if ( + line.qty_to_invoice > 0 + or (line.qty_to_invoice < 0 and final) + or line.display_type == "line_note" + ): + if line.is_downpayment: + down_payments += line + continue + if current_section_vals: + invoice_lines_vals.append(current_section_vals) + current_section_vals = None + prepared_line = line._prepare_invoice_line( + sequence=invoice_item_sequence + + folio.sale_line_ids.ids.index(line.id), + qty=lines_to_invoice[line.id], + ) + invoice_lines_vals.append(prepared_line) + + # If down payments are present in SO, group them under common section + if down_payments: + down_payments_section = folio._prepare_down_payment_section_line( + sequence=invoice_item_sequence + ) + invoice_lines_vals.append(down_payments_section) + for down_payment in down_payments: + # If the down payment is not for the current partner, skip it + # it will be managed manually or by the automatic invoice cron + if down_payment.default_invoice_to.id != group["partner_id"]: + continue + invoice_item_sequence += 1 + invoice_down_payment_vals = down_payment._prepare_invoice_line( + sequence=invoice_item_sequence + + folio.sale_line_ids.ids.index(down_payment.id) + ) + invoice_lines_vals.append(invoice_down_payment_vals) + + invoice_vals["invoice_line_ids"] = [ + (0, 0, invoice_line_id) for invoice_line_id in invoice_lines_vals + ] + + invoice_vals_list.append(invoice_vals) + invoice_item_sequence += 1000 + return invoice_vals_list + + def _get_groups_invoice_lines(self, lines_to_invoice, partner_invoice_id=False): + self.ensure_one() + groups_invoice_lines = [] + if partner_invoice_id: + groups_invoice_lines.append( + { + "partner_id": partner_invoice_id, + "lines": lines_to_invoice, + } + ) + else: + partners = lines_to_invoice.mapped("default_invoice_to") + for partner in partners: + groups_invoice_lines.append( + { + "partner_id": partner.id, + "lines": lines_to_invoice.filtered( + lambda l: l.default_invoice_to == partner + ), + } + ) + if any(not line.default_invoice_to for line in lines_to_invoice): + groups_invoice_lines.append( + { + "partner_id": self.env.ref("pms.various_pms_partner").id, + "lines": lines_to_invoice.filtered( + lambda l: not l.default_invoice_to + ), + } + ) + return groups_invoice_lines + + def _get_tax_amount_by_group(self): + self.ensure_one() + res = {} + for line in self.sale_line_ids: + price_reduce = line.price_total + product = line.product_id + taxes = line.tax_ids.compute_all(price_reduce, quantity=1, product=product)[ + "taxes" + ] + for tax in line.tax_ids: + group = tax.tax_group_id + res.setdefault(group, {"amount": 0.0, "base": 0.0}) + for t in taxes: + if t["id"] == tax.id or t["id"] in tax.children_tax_ids.ids: + res[group]["amount"] += t["amount"] + res[group]["base"] += t["base"] + res = sorted(res.items(), key=lambda line: line[0].sequence) + res = [ + (line[0].name, line[1]["amount"], line[1]["base"], len(res)) for line in res + ] + return res + + @api.depends("reservation_ids", "reservation_ids.external_reference") + def _compute_external_reference(self): + for folio in self: + folio.external_reference = folio._get_folio_external_reference() + + def _get_folio_external_reference(self): + self.ensure_one() + references = list(set(self.reservation_ids.mapped("external_reference"))) + references = list(filter(bool, references)) + if references: + return ",".join(references) + else: + return False + + @api.depends("reservation_ids", "reservation_ids.state") + def _compute_number_of_rooms(self): + for folio in self: + folio.number_of_rooms = len( + folio.reservation_ids.filtered(lambda a: a.state != "cancel") + ) + + @api.depends("reservation_ids", "reservation_ids.state") + def _compute_number_of_cancelled_rooms(self): + for folio in self: + folio.number_of_cancelled_rooms = len( + folio.reservation_ids.filtered(lambda a: a.state == "cancel") + ) + + @api.depends("service_ids", "service_ids.product_qty") + def _compute_number_of_services(self): + for folio in self: + folio.number_of_services = sum(folio.service_ids.mapped("product_qty")) + + @api.depends( + "reservation_ids", + "service_ids", + "service_ids.reservation_id", + "service_ids.default_invoice_to", + "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.reservation_line_ids.default_invoice_to", + "reservation_ids.tax_ids", + "reservation_ids.state", + ) + def _compute_sale_line_ids(self): + for folio in self.filtered(lambda f: isinstance(f.id, int)): + sale_lines_vals = [] + if folio.reservation_type in ("normal", "staff"): + sale_lines_vals_to_drop = [] + seq = 0 + for reservation in sorted( + folio.reservation_ids.filtered(lambda r: isinstance(r.id, int)), + key=lambda r: r.folio_sequence, + ): + seq += reservation.folio_sequence + # RESERVATION LINES + reservation_sale_lines = [] + reservation_sale_lines_to_drop = [] + if reservation.reservation_line_ids: + ( + reservation_sale_lines, + reservation_sale_lines_to_drop, + ) = self._get_reservation_sale_lines( + folio, reservation, sequence=seq + ) + if reservation_sale_lines: + sale_lines_vals.extend(reservation_sale_lines) + if reservation_sale_lines_to_drop: + sale_lines_vals_to_drop.extend(reservation_sale_lines_to_drop) + seq += len(reservation_sale_lines) + # RESERVATION SERVICES + service_sale_lines = [] + service_sale_lines_to_drop = [] + if reservation.service_ids: + ( + service_sale_lines, + service_sale_lines_to_drop, + ) = self._get_service_sale_lines( + folio, + reservation, + sequence=seq, + ) + if service_sale_lines: + sale_lines_vals.extend(service_sale_lines) + if service_sale_lines_to_drop: + sale_lines_vals_to_drop.extend(service_sale_lines_to_drop) + seq += len(service_sale_lines) + # FOLIO SERVICES + if folio.service_ids.filtered(lambda r: not r.reservation_id): + service_sale_lines = False + service_sale_lines_to_drop = False + ( + service_sale_lines, + service_sale_lines_to_drop, + ) = self._get_folio_services_sale_lines(folio, sequence=seq + 1) + if service_sale_lines: + sale_lines_vals.extend(service_sale_lines) + if service_sale_lines_to_drop: + sale_lines_vals_to_drop.extend(service_sale_lines_to_drop) + if sale_lines_vals: + folio.sale_line_ids = sale_lines_vals + if sale_lines_vals_to_drop: + self.env["folio.sale.line"].browse(sale_lines_vals_to_drop).unlink() + if not sale_lines_vals: + folio.sale_line_ids = False + + @api.depends("pms_property_id") + def _compute_company_id(self): + for record in self: + record.company_id = record.pms_property_id.company_id + + @api.depends( + "partner_id", "agency_id", "reservation_ids", "reservation_ids.pricelist_id" + ) + def _compute_pricelist_id(self): + for folio in self: + is_new = not folio.pricelist_id or isinstance(folio.id, models.NewId) + if folio.reservation_type == "out": + folio.pricelist_id = False + elif len(folio.reservation_ids.pricelist_id) == 1: + folio.pricelist_id = folio.reservation_ids.pricelist_id + elif is_new and folio.agency_id and folio.agency_id.apply_pricelist: + folio.pricelist_id = folio.agency_id.property_product_pricelist + elif ( + is_new + and folio.partner_id + and folio.partner_id.property_product_pricelist + and folio.partner_id.property_product_pricelist.is_pms_available + ): + folio.pricelist_id = folio.partner_id.property_product_pricelist + elif not folio.pricelist_id: + folio.pricelist_id = folio.pms_property_id.default_pricelist_id + + @api.depends( + "agency_id", + "reservation_type", + "partner_name", + "email", + "mobile", + ) + def _compute_partner_id(self): + for folio in self: + if folio.reservation_type == "out": + folio.partner_id = False + elif folio.agency_id and folio.invoice_to_agency: + folio.partner_id = folio.agency_id + elif not folio.partner_id: + folio.partner_id = False + + @api.depends("pms_property_id") + def _compute_user_id(self): + active_user_id = self.env.uid + for folio in self: + if not folio.user_id: + property_users = folio.pms_property_id.member_ids.filtered( + lambda u: u.pms_role == "reception" + ).mapped("user_id") + if property_users: + if active_user_id in property_users.ids: + folio.user_id = active_user_id + elif property_users: + folio.user_id = property_users[0] + else: + folio.user_id = active_user_id or folio.pms_property_id.user_id + + @api.depends("pms_property_id") + def _compute_revenue_user_id(self): + for folio in self: + revenue_users = folio.pms_property_id.member_ids.filtered( + lambda u: u.pms_role == "revenue" + ).mapped("user_id") + if revenue_users: + folio.revenue_user_id = revenue_users[0] + else: + folio.revenue_user_id = False + + @api.depends("pms_property_id") + def _compute_administrative_user_id(self): + for folio in self: + administrative_users = folio.pms_property_id.member_ids.filtered( + lambda u: u.pms_role == "administrative" + ).mapped("user_id") + if administrative_users: + folio.administrative_user_id = administrative_users[0] + else: + folio.administrative_user_id = False + + @api.depends("pms_property_id") + def _compute_manager_user_id(self): + for folio in self: + manager_users = folio.pms_property_id.member_ids.filtered( + lambda u: u.pms_role == "manager" + ).mapped("user_id") + if manager_users: + folio.manager_user_id = manager_users[0] + else: + folio.manager_user_id = False + + @api.depends( + "partner_id", + "reservation_ids", + "reservation_ids.partner_id", + "reservation_ids.checkin_partner_ids", + "reservation_ids.checkin_partner_ids.partner_id", + ) + def _compute_partner_invoice_ids(self): + for folio in self: + if folio.partner_id: + addr = folio.partner_id.address_get(["invoice"]) + if not addr["invoice"] in folio.partner_invoice_ids.ids: + folio.partner_invoice_ids = [(4, addr["invoice"])] + for reservation in folio.reservation_ids: + if reservation.partner_id: + addr = reservation.partner_id.address_get(["invoice"]) + if not addr["invoice"] in folio.partner_invoice_ids.ids: + folio.partner_invoice_ids = [(4, addr["invoice"])] + for checkin in reservation.checkin_partner_ids: + if checkin.partner_id: + addr = checkin.partner_id.address_get(["invoice"]) + if not addr["invoice"] in folio.partner_invoice_ids.ids: + folio.partner_invoice_ids = [(4, addr["invoice"])] + self.filtered(lambda f: not f.partner_invoice_ids).partner_invoice_ids = False + + @api.depends("partner_id") + def _compute_payment_term_id(self): + self.payment_term_id = False + for folio in self: + folio.payment_term_id = ( + folio.partner_id.property_payment_term_id + and folio.partner_id.property_payment_term_id.id + or False + ) + + @api.depends("reservation_ids", "reservation_ids.commission_amount") + def _compute_commission(self): + for folio in self: + folio.commission = 0 + for reservation in folio.reservation_ids: + if reservation.commission_amount != 0: + folio.commission = folio.commission + reservation.commission_amount + + @api.depends( + "reservation_ids", + "reservation_ids.sale_channel_ids", + "service_ids", + "service_ids.sale_channel_origin_id", + ) + def _compute_sale_channel_ids(self): + for record in self: + sale_channel_ids = [] + if record.reservation_ids: + for sale in record.reservation_ids.mapped("sale_channel_ids.id"): + sale_channel_ids.append(sale) + if record.service_ids: + for sale in record.service_ids.mapped("sale_channel_origin_id.id"): + sale_channel_ids.append(sale) + sale_channel_ids = list(set(sale_channel_ids)) + record.sale_channel_ids = [(6, 0, sale_channel_ids)] + + @api.depends("sale_line_ids.invoice_lines") + def _compute_get_invoiced(self): + # The invoice_ids are obtained thanks to the invoice lines of the SO + # lines, and we also search for possible refunds created directly from + # existing invoices. This is necessary since such a refund is not + # directly linked to the SO. + for order in self: + invoices = order.sale_line_ids.invoice_lines.move_id.filtered( + lambda r: r.move_type + in ("out_invoice", "out_refund", "out_receipt", "in_receipt") + ) + order.move_ids = invoices + order.invoice_count = len(invoices) + + # @api.depends( + # "reservation_ids", + # "reservation_ids.currency_id" + # ) + # def _compute_currency_id(self): + # if len(self.reservation_ids.mapped("currency_id")) == 1: + # self.currency_id = self.reservation_ids.mapped("currency_id") + # else: + # raise UserError(_("Some reservations have different currency")) + + # is_checkin = fields.Boolean() + + def _compute_access_url(self): + super(PmsFolio, self)._compute_access_url() + for folio in self: + folio.access_url = "/my/folios/%s" % (folio.id) + + @api.depends("state", "sale_line_ids.invoice_status", "force_nothing_to_invoice") + def _compute_get_invoice_status(self): + """ + Compute the invoice status of a Folio. Possible statuses: + - no: if the Folio is in status 'draft', we consider that there is nothing to + invoice. This is also the default value if the conditions of no + other status is met. + - to_invoice: if any SO line is 'to_invoice', the whole SO is 'to_invoice' + - invoiced: if all SO lines are invoiced, the SO is invoiced. + """ + unconfirmed_orders = self.filtered(lambda folio: folio.state in ["draft"]) + unconfirmed_orders.invoice_status = "no" + zero_orders = self.filtered(lambda folio: folio.amount_total == 0) + confirmed_orders = self - unconfirmed_orders - zero_orders + if not confirmed_orders: + return + line_invoice_status_all = [ + (d["folio_id"][0], d["invoice_status"]) + for d in self.env["folio.sale.line"].read_group( + [ + ("folio_id", "in", confirmed_orders.ids), + ("is_downpayment", "=", False), + ("display_type", "=", False), + ], + ["folio_id", "invoice_status"], + ["folio_id", "invoice_status"], + lazy=False, + ) + ] + for order in confirmed_orders: + line_invoice_status = [ + d[1] for d in line_invoice_status_all if d[0] == order.id + ] + if not order.force_nothing_to_invoice and any( + invoice_status == "to_invoice" for invoice_status in line_invoice_status + ): + order.invoice_status = "to_invoice" + elif any(inv.state == "draft" for inv in order.move_ids): + order.invoice_status = "to_confirm" + elif line_invoice_status and any( + invoice_status == "invoiced" for invoice_status in line_invoice_status + ): + order.invoice_status = "invoiced" + else: + order.invoice_status = "no" + + @api.depends("untaxed_amount_to_invoice") + def _compute_force_nothing_to_invoice(self): + # If the invoice amount and amount total are the same, + # and the qty to invoice is not 0, we force nothing to invoice + for order in self: + if ( + order.untaxed_amount_to_invoice <= 0 + and sum(order.sale_line_ids.mapped("qty_to_invoice")) != 0 + ): + order.force_nothing_to_invoice = True + else: + order.force_nothing_to_invoice = False + + @api.depends("partner_id", "partner_id.name", "agency_id", "reservation_type") + def _compute_partner_name(self): + for record in self: + if record.partner_id and record.partner_id != record.agency_id: + record.partner_name = record.partner_id.name + elif record.agency_id and not record.partner_name: + # if the customer not is the agency but we dont know the customer's name, + # set the name provisional + record.partner_name = _("Reservation from ") + record.agency_id.name + elif not record.partner_name: + record.partner_name = False + + @api.depends("partner_id", "partner_id.email", "agency_id") + def _compute_email(self): + for record in self: + self._apply_email(record) + + @api.depends("partner_id", "partner_id.mobile", "agency_id") + def _compute_mobile(self): + for record in self: + self._apply_mobile(record) + + @api.depends( + "partner_name", + "email", + "mobile", + "partner_id", + ) + def _compute_partner_incongruences(self): + fields_mapping = { + "partner_name": "name", + "email": "email", + "mobile": "mobile", + } + for record in self: + incongruous_fields = False + if record.partner_id: + for k, v in fields_mapping.items(): + if record.partner_id[v] and record.partner_id[v] != record[k]: + if not incongruous_fields: + incongruous_fields = v + else: + incongruous_fields += ", " + v + if incongruous_fields: + record.partner_incongruences = ( + incongruous_fields + " field/s don't correspond to saved host" + ) + else: + record.partner_incongruences = False + else: + record.partner_incongruences = False + + @api.depends("sale_line_ids.price_total") + def _compute_amount_all(self): + """ + Compute the total amounts of the SO. + """ + for folio in self: + amount_untaxed = amount_tax = 0.0 + for line in folio.sale_line_ids: + amount_untaxed += line.price_subtotal + amount_tax += line.price_tax + folio.update( + { + "amount_untaxed": amount_untaxed, + "amount_tax": amount_tax, + "amount_total": amount_untaxed + amount_tax, + } + ) + + @api.depends("reservation_ids", "reservation_ids.state") + def _compute_count_rooms_pending_arrival(self): + self.count_rooms_pending_arrival = 0 + for folio in self.filtered("reservation_ids"): + folio.count_rooms_pending_arrival = len( + folio.reservation_ids.filtered( + lambda c: c.state in ("draf", "confirm", "arrival_delayed") + ) + ) + + @api.depends("checkin_partner_ids", "checkin_partner_ids.state") + def _compute_pending_checkin_data(self): + for folio in self: + if folio.reservation_type != "out": + folio.pending_checkin_data = len( + folio.checkin_partner_ids.filtered(lambda c: c.state == "draft") + ) + + @api.depends("pending_checkin_data") + def _compute_ratio_checkin_data(self): + self.ratio_checkin_data = 0 + for folio in self.filtered("reservation_ids"): + if folio.reservation_type != "out": + folio.ratio_checkin_data = ( + ( + sum(folio.reservation_ids.mapped("adults")) + - folio.pending_checkin_data + ) + * 100 + / sum(folio.reservation_ids.mapped("adults")) + ) + + @api.depends("sale_line_ids.untaxed_amount_to_invoice") + def _compute_untaxed_amount_to_invoice(self): + for folio in self: + folio.untaxed_amount_to_invoice = sum( + folio.sale_line_ids.filtered(lambda l: not l.is_downpayment).mapped( + "untaxed_amount_to_invoice" + ) + ) + + # TODO: Add return_ids to depends + @api.depends( + "amount_total", + "currency_id", + "company_id", + "reservation_type", + "state", + "payment_ids.state", + "payment_ids.move_id", + "payment_ids.move_id.line_ids", + "payment_ids.move_id.line_ids.date", + "payment_ids.move_id.line_ids.debit", + "payment_ids.move_id.line_ids.credit", + "payment_ids.move_id.line_ids.currency_id", + "payment_ids.move_id.line_ids.amount_currency", + "move_ids.amount_residual", + ) + def _compute_amount(self): + for record in self: + if record.reservation_type == "out": + record.amount_total = 0 + vals = { + "payment_state": "nothing_to_pay", + "pending_amount": 0, + "invoices_paid": 0, + } + record.update(vals) + else: + # first attempt compute amount search payments refs with only one folio + mls_one_folio = ( + record.payment_ids.filtered(lambda pay: len(pay.folio_ids) == 1) + .mapped("move_id.line_ids") + .filtered( + lambda x: x.account_id.internal_type == "receivable" + and x.parent_state == "posted" + ) + ) + advance_amount = record._get_advance_amount(mls_one_folio) + # Compute 'payment_state'. + vals = record._get_amount_vals(mls_one_folio, advance_amount) + # If folio its not paid, search payments refs with more than one folio + folio_ids = record.payment_ids.mapped("folio_ids.id") + if vals["pending_amount"] > 0 and len(folio_ids) > 1: + folios = self.env["pms.folio"].browse(folio_ids) + mls_multi_folio = folios.payment_ids.mapped( + "move_id.line_ids" + ).filtered( + lambda x: x.account_id.internal_type == "receivable" + and x.parent_state == "posted" + ) + if mls_multi_folio: + advance_amount = record._get_advance_amount(mls_multi_folio) + vals = record._get_amount_vals( + mls_multi_folio, advance_amount, folio_ids + ) + + record.update(vals) + + def _get_advance_amount(self, mls): + self.ensure_one() + advance_amount = 0.0 + for line in mls: + line_currency = line.currency_id or line.company_id.currency_id + line_amount = line.amount_currency if line.currency_id else line.balance + line_amount *= -1 + if line_currency != self.currency_id: + advance_amount += line.currency_id._convert( + line_amount, + self.currency_id, + self.company_id, + line.date or fields.Date.today(), + ) + else: + advance_amount += line_amount + return advance_amount + + def _get_amount_vals(self, mls, advance_amount, folio_ids=False): + self.ensure_one() + folios = self + if folio_ids: + folios = self.env["pms.folio"].browse(folio_ids) + mls_one_folio = ( + self.payment_ids.filtered(lambda pay: len(pay.folio_ids) == 1) + .mapped("move_id.line_ids") + .filtered( + lambda x: x.account_id.internal_type == "receivable" + and x.parent_state == "posted" + ) + ) + amount_folio_residual = self.amount_total - self._get_advance_amount( + mls_one_folio + ) + amount_total_residual = sum(folios.mapped("amount_total")) - advance_amount + else: + amount_folio_residual = amount_total_residual = ( + sum(folios.mapped("amount_total")) - advance_amount + ) + total = sum(folios.mapped("amount_total")) + + # REVIEW: Must We ignored services in cancelled folios + # pending amount? + # for folio in folios: + # if folio.state == "cancel": + # total = total - sum(folio.service_ids.mapped("price_total")) + payment_state = "not_paid" + if ( + mls + and float_compare( + amount_total_residual, + total, + precision_rounding=self.currency_id.rounding, + ) + != 0 + ): + has_due_amount = float_compare( + amount_total_residual, + 0.0, + precision_rounding=self.currency_id.rounding, + ) + if has_due_amount == 0: + payment_state = "paid" + elif has_due_amount > 0: + payment_state = "partial" + elif has_due_amount < 0: + payment_state = "overpayment" + elif total == 0: + payment_state = "nothing_to_pay" + + vals = { + "payment_multi": len(folios) > 1, + "pending_amount": min(amount_total_residual, amount_folio_residual), + "invoices_paid": advance_amount, + "payment_state": payment_state, + } + return vals + + @api.depends("reservation_ids", "reservation_ids.priority") + def _compute_max_reservation_priority(self): + for record in self.filtered("reservation_ids"): + reservation_priors = record.reservation_ids.mapped("priority") + record.max_reservation_priority = max(reservation_priors) + + def _compute_checkin_partner_count(self): + for record in self: + if ( + record.reservation_type in ("normal", "staff") + and record.reservation_ids + ): + filtered_reservs = record.reservation_ids.filtered( + lambda x: x.state != "cancel" + ) + mapped_checkin_partner = filtered_reservs.mapped( + "checkin_partner_ids.id" + ) + record.checkin_partner_count = len(mapped_checkin_partner) + mapped_checkin_partner_count = filtered_reservs.mapped( + lambda x: (x.adults + x.children) - len(x.checkin_partner_ids) + ) + record.checkin_partner_pending_count = sum(mapped_checkin_partner_count) + + @api.depends("email", "mobile", "partner_name") + def _compute_possible_existing_customer_ids(self): + for record in self: + record.possible_existing_customer_ids = False + if record.partner_name: + possible_customer = self._apply_possible_existing_customer_ids( + record.email, record.mobile, record.partner_id + ) + if possible_customer: + record.possible_existing_customer_ids = possible_customer + + @api.depends("reservation_ids", "reservation_ids.checkin") + def _compute_first_checkin(self): + for record in self: + if record.reservation_ids: + checkins = record.reservation_ids.mapped("checkin") + record.first_checkin = min(checkins) + + def _compute_days_to_checkin(self): + for record in self: + record.days_to_checkin = (record.first_checkin - fields.Date.today()).days + + def _search_days_to_checkin(self, operator, value): + target_date = fields.Date.today() + datetime.timedelta(days=value) + if operator in ("=", ">=", ">", "<=", "<"): + return [("first_checkin", operator, target_date)] + raise UserError( + _("Unsupported operator %s for searching on date") % (operator,) + ) + + @api.depends("reservation_ids", "reservation_ids.checkout") + def _compute_last_checkout(self): + for record in self: + if record.reservation_ids: + checkouts = record.reservation_ids.mapped("checkout") + record.last_checkout = max(checkouts) + + def _compute_days_to_checkout(self): + for record in self: + record.days_to_checkout = (record.last_checkout - fields.Date.today()).days + + def _search_days_to_checkout(self, operator, value): + target_date = fields.Date.today() + datetime.timedelta(days=value) + if operator in ("=", ">=", ">", "<=", "<"): + return [("last_checkout", operator, target_date)] + raise UserError( + _("Unsupported operator %s for searching on date") % (operator,) + ) + + @api.depends("agency_id") + def _compute_invoice_to_agengy(self): + for record in self: + if not record.agency_id or record.agency_id.invoice_to_agency == "never": + record.invoice_to_agency = False + elif record.agency_id.invoice_to_agency == "always": + record.invoice_to_agency = True + elif not record.invoice_to_agency: + record.invoice_to_agency = False + + @api.depends("partner_id") + def _compute_lang(self): + for record in self.filtered("partner_id"): + record.lang = record.partner_id.lang + + def _search_invoice_ids(self, operator, value): + if operator == "in" and value: + self.env.cr.execute( + """ + SELECT array_agg(fo.id) + FROM pms_folio fo + JOIN folio_sale_line fol ON fol.folio_id = fo.id + JOIN folio_sale_line_invoice_rel foli_rel ON foli_rel.sale_line_id = fol.id + JOIN account_move_line aml ON aml.id = foli_rel.invoice_line_id + JOIN account_move am ON am.id = aml.move_id + WHERE + am.move_type in ('out_invoice', 'out_refund', 'in_receipt') AND + am.id = ANY(%s) + """, + (list(value),), + ) + so_ids = self.env.cr.fetchone()[0] or [] + return [("id", "in", so_ids)] + return [ + "&", + ( + "sale_line_ids.invoice_lines.move_id.move_type", + "in", + ("out_invoice", "out_refund", "in_receipt"), + ), + ("sale_line_ids.invoice_lines.move_id", operator, value), + ] + + @api.constrains("name") + def _check_required_partner_name(self): + for record in self: + if not record.partner_name and record.reservation_type != "out": + raise models.ValidationError(_("You must assign a customer name")) + + @api.model + def create(self, vals): + if vals.get("name", _("New")) == _("New") or "name" not in vals: + pms_property_id = ( + self.env.user.get_active_property_ids()[0] + if "pms_property_id" not in vals + else vals["pms_property_id"] + ) + pms_property = self.env["pms.property"].browse(pms_property_id) + vals["name"] = pms_property.folio_sequence_id._next_do() + result = super(PmsFolio, self).create(vals) + result.access_token = result._portal_ensure_token() + return result + + def write(self, vals): + reservations_to_update = self.env["pms.reservation"] + services_to_update = self.env["pms.service"] + if "sale_channel_origin_id" in vals: + reservations_to_update = self.get_reservations_to_update_channel(vals) + services_to_update = self.get_services_to_update_channel(vals) + + res = super(PmsFolio, self).write(vals) + if reservations_to_update: + reservations_to_update.sale_channel_origin_id = vals[ + "sale_channel_origin_id" + ] + + if services_to_update: + services_to_update.sale_channel_origin_id = vals["sale_channel_origin_id"] + + return res + + @api.model + def _get_languages(self): + return self.env["res.lang"].get_installed() + + def get_reservations_to_update_channel(self, vals): + reservations_to_update = self.env["pms.reservation"] + for record in self: + for reservation in record.reservation_ids: + if ( + reservation.sale_channel_origin_id == self.sale_channel_origin_id + ) and ( + vals["sale_channel_origin_id"] + != reservation.sale_channel_origin_id.id + ): + reservations_to_update += reservation + return reservations_to_update + + def get_services_to_update_channel(self, vals): + services_to_update = self.env["pms.service"] + for record in self: + for service in record.service_ids: + if ( + not service.reservation_id + and (service.sale_channel_origin_id == self.sale_channel_origin_id) + and ( + vals["sale_channel_origin_id"] + != service.sale_channel_origin_id.id + ) + ): + services_to_update += service + return services_to_update + + def action_pay(self): + self.ensure_one() + self.ensure_one() + partner = self.partner_id.id + amount = self.pending_amount + view_id = self.env.ref("pms.wizard_payment_folio_view_form").id + return { + "name": _("Register Payment"), + "view_type": "form", + "view_mode": "form", + "res_model": "wizard.payment.folio", + "type": "ir.actions.act_window", + "view_id": view_id, + "context": { + "default_folio_id": self.id, + "default_amount": amount, + "default_partner_id": partner, + }, + "target": "new", + } + + def open_partner(self): + """Utility method used to add an "View Customer" button in folio views""" + self.ensure_one() + partner_form_id = self.env.ref("pms.view_partner_data_form").id + return { + "type": "ir.actions.act_window", + "res_model": "res.partner", + "view_mode": "form", + "views": [(partner_form_id, "form")], + "res_id": self.partner_id.id, + "target": "new", + "flags": {"form": {"action_buttons": True}}, + } + + def open_moves_folio(self): + invoices = self.mapped("move_ids") + action = self.env.ref("account.action_move_out_invoice_type").sudo().read()[0] + if len(invoices) > 1: + action["domain"] = [("id", "in", invoices.ids)] + elif len(invoices) == 1: + action["views"] = [(self.env.ref("account.view_move_form").id, "form")] + action["res_id"] = invoices.ids[0] + else: + action = {"type": "ir.actions.act_window_close"} + return action + + def folio_multi_changes(self): + self.ensure_one() + reservation_ids = self.reservation_ids.ids + action = self.env.ref("pms.action_folio_changes").sudo().read()[0] + action["context"] = ({"default_reservation_ids": [(6, 0, reservation_ids)]},) + return action + + def action_checks(self): + self.ensure_one() + rooms = self.mapped("reservation_ids.id") + return { + "name": _("Checkins"), + "view_type": "form", + "view_mode": "tree,form", + "res_model": "pms.checkin.partner", + "type": "ir.actions.act_window", + "domain": [("reservation_id", "in", rooms)], + "search_view_id": [ + self.env.ref("pms.pms_checkin_partner_view_folio_search").id, + "search", + ], + "target": "new", + } + + def action_to_arrive(self): + self.ensure_one() + reservations = self.reservation_ids.filtered( + lambda c: c.state in ("draf", "confirm", "arrival_delayed") + ) + action = self.env.ref("pms.open_pms_reservation_form_tree_all").read()[0] + action["domain"] = [("id", "in", reservations.ids)] + return action + + def action_done(self): + reservation_ids = self.mapped("reservation_ids") + for line in reservation_ids: + if line.state == "onboard": + line.action_reservation_checkout() + + def action_cancel(self): + for folio in self: + for reservation in folio.reservation_ids.filtered( + lambda res: res.state != "cancel" + ): + reservation.action_cancel() + self.write( + { + "state": "cancel", + } + ) + return True + + def action_confirm(self): + self.filtered(lambda x: x.state != "confirm").write( + {"state": "confirm", "confirmation_date": fields.Datetime.now()} + ) + + if self.env.context.get("confirm_all_reservations"): + self.reservation_ids.action_confirm() + + return True + + # MAIL FLOWS + + def action_open_confirmation_mail_composer(self): + self.ensure_one() + res_id = False + res_ids = [] + partner_ids = [] + if self.pms_property_id.property_confirmed_template: + template = self.pms_property_id.property_confirmed_template + else: + raise ValidationError( + _( + "You must select a confirmation template " + "in the email configuration menu of the property" + ) + ) + model = "pms.folio" + partner_ids = [self.partner_id.id] + res_id = self.id + composition_mode = "comment" + ctx = dict( + model=model, + default_model=model, + default_template_id=template and template.id or False, + default_composition_mode=composition_mode, + partner_ids=partner_ids, + force_email=True, + ) + return self.action_open_mail_composer(ctx, res_id=res_id, res_ids=res_ids) + + def action_open_modification_mail_composer(self): + self.ensure_one() + res_id = False + res_ids = [] + partner_ids = [] + if self.pms_property_id.property_modified_template: + template = self.pms_property_id.property_modified_template + else: + raise ValidationError( + _( + "You must select a modification template " + "in the email configuration menu of the property" + ) + ) + model = "pms.folio" + partner_ids = [self.partner_id.id] + res_id = self.id + composition_mode = "comment" + + ctx = dict( + model=model, + default_model=model, + default_template_id=template and template.id or False, + default_composition_mode=composition_mode, + partner_ids=partner_ids, + force_email=True, + ) + return self.action_open_mail_composer(ctx, res_id=res_id, res_ids=res_ids) + + def action_open_exit_mail_composer(self): + self.ensure_one() + res_id = False + res_ids = [] + partner_ids = [] + + if self.pms_property_id.property_exit_template: + template = self.pms_property_id.property_exit_template + else: + raise ValidationError( + _( + "You must select a exit template in " + "the email configuration menu of the property" + ) + ) + model = "pms.checkin.partner" + composition_mode = "mass_mail" + for checkin_partner in self.checkin_partner_ids: + if checkin_partner.state == "done": + partner_ids.append(checkin_partner.partner_id.id) + res_ids.append(checkin_partner.id) + ctx = dict( + model=model, + default_model=model, + default_template_id=template and template.id or False, + default_composition_mode=composition_mode, + partner_ids=partner_ids, + force_email=True, + ) + return self.action_open_mail_composer(ctx, res_id=res_id, res_ids=res_ids) + + def action_open_cancelation_mail_composer(self): + self.ensure_one() + res_id = False + res_ids = [] + partner_ids = [] + if self.pms_property_id.property_canceled_template: + template = self.pms_property_id.property_canceled_template + else: + raise ValidationError( + _( + "You must select a cancelation template " + "in the email configuration menu of the property" + ) + ) + model = "pms.reservation" + composition_mode = "mass_mail" + for reservation in self.reservation_ids: + if reservation.state == "cancel": + partner_ids.append(reservation.partner_id.id) + res_ids.append(reservation.id) + ctx = dict( + model=model, + default_model=model, + default_template_id=template and template.id or False, + default_composition_mode=composition_mode, + partner_ids=partner_ids, + force_email=True, + ) + return self.action_open_mail_composer(ctx, res_id=res_id, res_ids=res_ids) + + def action_open_mail_composer(self, ctx, res_id=False, res_ids=False): + compose_form = self.env.ref( + "mail.email_compose_message_wizard_form", raise_if_not_found=False + ) + composition_mode = ctx.get("default_composition_mode") + if composition_mode == "comment": + ctx.update( + default_res_id=res_id, + record_id=res_id, + ) + else: + ctx.update( + active_ids=res_ids, + ) + return { + "name": _("Send Mail "), + "type": "ir.actions.act_window", + "view_type": "form", + "view_mode": "form", + "res_model": "mail.compose.message", + "views": [(compose_form.id, "form")], + "view_id": compose_form.id, + "target": "new", + "context": ctx, + } + + def _message_post_after_hook(self, message, msg_vals): + res = super(PmsFolio, self)._message_post_after_hook(message, msg_vals) + for folio in self: + for follower in folio.message_follower_ids: + follower.sudo().unlink() + return res + + def action_view_invoice(self): + invoices = self.mapped("move_ids") + action = self.env["ir.actions.actions"]._for_xml_id( + "account.action_move_out_invoice_type" + ) + if len(invoices) > 1: + action["domain"] = [("id", "in", invoices.ids)] + elif len(invoices) == 1: + form_view = [(self.env.ref("account.view_move_form").id, "form")] + if "views" in action: + action["views"] = form_view + [ + (state, view) for state, view in action["views"] if view != "form" + ] + else: + action["views"] = form_view + action["res_id"] = invoices.id + else: + action = {"type": "ir.actions.act_window_close"} + + context = { + "default_move_type": "out_invoice", + } + if len(self) == 1: + context.update( + { + "default_partner_id": self.partner_id.id, + "default_invoice_payment_term_id": self.payment_term_id.id + or self.partner_id.property_payment_term_id.id + or self.env["account.move"] + .default_get(["invoice_payment_term_id"]) + .get("invoice_payment_term_id"), + "default_invoice_origin": self.mapped("name"), + "default_user_id": self.user_id.id, + } + ) + action["context"] = context + return action + + def preview_folio(self): + self.ensure_one() + return { + "type": "ir.actions.act_url", + "target": "self", + "url": self.get_portal_url(), + } + + # flake8:noqa=C901 + def _create_invoices( + self, + grouped=False, + final=False, + date=None, + lines_to_invoice=False, + partner_invoice_id=False, + ): + """ + Create the invoice associated to the Folio. + :param grouped: if True, invoices are grouped by Folio id. + If False, invoices are grouped by + (partner_invoice_ids, currency) + :param final: if True, refunds will be generated if necessary + :param lines_to_invoice: invoice specific lines dict(key=id, value=qty). + if False, invoice all + :returns: list of created invoices + """ + if not self.env["account.move"].check_access_rights("create", False): + try: + self.check_access_rights("write") + self.check_access_rule("write") + except AccessError: + return self.env["account.move"] + # 1) Create invoices. + if not lines_to_invoice: + self = self.with_context(lines_auto_add=True) + lines_to_invoice = dict() + for line in self.sale_line_ids.filtered( + lambda l: l.qty_to_invoice > 0 + or (l.qty_to_invoice < 0 and final) + or l.display_type == "line_note" + ): + if not self._context.get("autoinvoice"): + lines_to_invoice[line.id] = ( + 0 if line.display_type else line.qty_to_invoice + ) + elif ( + line.autoinvoice_date + and line.autoinvoice_date <= fields.Date.today() + ): + lines_to_invoice[line.id] = ( + 0 if line.display_type else line.qty_to_invoice + ) + invoice_vals_list = self.get_invoice_vals_list( + final=final, + lines_to_invoice=lines_to_invoice, + partner_invoice_id=partner_invoice_id, + ) + if not invoice_vals_list: + raise self._nothing_to_invoice_error() + + # 2) Manage 'grouped' parameter: group by (partner_id, currency_id). + if not grouped: + invoice_vals_list = self._get_group_vals_list(invoice_vals_list) + + partner_invoice = self.env["res.partner"].browse(partner_invoice_id) + partner_invoice_policy = self.pms_property_id.default_invoicing_policy + if partner_invoice and partner_invoice.invoicing_policy != "property": + partner_invoice_policy = partner_invoice.invoicing_policy + invoice_date = False + if date: + invoice_date = date + if partner_invoice_policy == "checkout": + margin_days_autoinvoice = ( + self.pms_property_id.margin_days_autoinvoice + if partner_invoice.margin_days_autoinvoice == 0 + else partner_invoice.margin_days_autoinvoice + ) + invoice_date = max( + self.env["pms.reservation"] + .search([("sale_line_ids", "in", lines_to_invoice.keys())]) + .mapped("checkout") + ) + datetime.timedelta(days=margin_days_autoinvoice) + if partner_invoice_policy == "month_day": + month_day = ( + self.pms_property_id.invoicing_month_day + if partner_invoice.invoicing_month_day == 0 + else partner_invoice.invoicing_month_day + ) + invoice_date = datetime.date( + datetime.date.today().year, + datetime.date.today().month, + month_day, + ) + if invoice_date < datetime.date.today(): + invoice_date = datetime.date( + datetime.date.today().year, + datetime.date.today().month + 1, + month_day, + ) + if invoice_date: + if ( + self.company_id.period_lock_date + and invoice_date < self.company_id.period_lock_date + and not self.user_has_groups("account.group_account_manager") + ): + raise UserError( + _( + "The period to create this invoice is locked. " + "Please contact your administrator to unlock it." + ) + ) + if invoice_date < datetime.date.today() and not self._context.get( + "autoinvoice" + ): + invoice_date = datetime.date.today() + key_field = ( + "invoice_date" + if invoice_date <= fields.Date.today() + else "invoice_date_due" + ) + for vals in invoice_vals_list: + vals["date"] = invoice_date + vals[key_field] = invoice_date + + # 3) Create invoices. + + # As part of the invoice creation, we make sure the + # sequence of multiple SO do not interfere + # in a single invoice. Example: + # Folio 1: + # - Section A (sequence: 10) + # - Product A (sequence: 11) + # Folio 2: + # - Section B (sequence: 10) + # - Product B (sequence: 11) + # + # If Folio 1 & 2 are grouped in the same invoice, + # the result will be: + # - Section A (sequence: 10) + # - Section B (sequence: 10) + # - Product A (sequence: 11) + # - Product B (sequence: 11) + # + # Resequencing should be safe, however we resequence only + # if there are less invoices than orders, meaning a grouping + # might have been done. This could also mean that only a part + # of the selected SO are invoiceable, but resequencing + # in this case shouldn't be an issue. + if len(invoice_vals_list) < len(self): + FolioSaleLine = self.env["folio.sale.line"] + for invoice in invoice_vals_list: + sequence = 1 + for line in invoice["invoice_line_ids"]: + line[2]["sequence"] = FolioSaleLine._get_invoice_line_sequence( + new=sequence, old=line[2]["sequence"] + ) + sequence += 1 + + # Manage the creation of invoices in sudo because + # a salesperson must be able to generate an invoice from a + # sale order without "billing" access rights. + # However, he should not be able to create an invoice from scratch. + moves = self._create_account_moves(invoice_vals_list) + + # 4) Some moves might actually be refunds: convert + # them if the total amount is negative + # We do this after the moves have been created + # since we need taxes, etc. to know if the total + # is actually negative or not + if final: + moves.sudo().filtered( + lambda m: m.amount_total < 0 + ).action_switch_invoice_into_refund_credit_note() + for move in moves: + move.sudo().message_post_with_view( + "mail.message_origin_link", + values={ + "self": move, + "origin": move.line_ids.mapped("folio_line_ids.folio_id"), + }, + subtype_id=self.env.ref("mail.mt_note").id, + ) + return moves + + def _create_account_moves(self, invoice_vals_list): + moves = self.env["account.move"] + for invoice_vals in invoice_vals_list: + if invoice_vals["move_type"] == "out_invoice": + move = ( + self.env["account.move"] + .sudo() + .with_context(default_move_type="out_invoice") + .create(invoice_vals) + ) + moves += move + return moves + + def _get_group_vals_list(self, invoice_vals_list): + new_invoice_vals_list = [] + invoice_grouping_keys = self._get_invoice_grouping_keys() + for _grouping_keys, invoices in groupby( + invoice_vals_list, + key=lambda x: [ + x.get(grouping_key) for grouping_key in invoice_grouping_keys + ], + ): + origins = set() + payment_refs = set() + refs = set() + ref_invoice_vals = None + for invoice_vals in invoices: + if not ref_invoice_vals: + ref_invoice_vals = invoice_vals + else: + ref_invoice_vals["invoice_line_ids"] += invoice_vals[ + "invoice_line_ids" + ] + origins.add(invoice_vals["invoice_origin"]) + payment_refs.add(invoice_vals["payment_reference"]) + refs.add(invoice_vals["ref"]) + ref_invoice_vals.update( + { + "ref": ", ".join(refs)[:2000], + "invoice_origin": ", ".join(origins), + "payment_reference": len(payment_refs) == 1 + and payment_refs.pop() + or False, + } + ) + new_invoice_vals_list.append(ref_invoice_vals) + return new_invoice_vals_list + + def _prepare_invoice(self, partner_invoice_id=False): + """ + Prepare the dict of values to create the new invoice for a folio. + This method may be overridden to implement custom invoice generation + (making sure to call super() to establish a clean extension chain). + """ + self.ensure_one() + journal = self.pms_property_id._get_folio_default_journal(partner_invoice_id) + if not journal: + journal = ( + self.env["account.move"] + .with_context( + default_move_type="out_invoice", + default_company_id=self.company_id.id, + default_pms_property_id=self.pms_property_id.id, + ) + ._search_default_journal() + ) + if not journal: + raise UserError( + _("Please define an accounting sales journal for the company %s (%s).") + % (self.company_id.name, self.company_id.id) + ) + ref = "" + if self.name: + ref = self.name + if self.external_reference: + ref += " - " + self.external_reference + invoice_vals = { + "name": "/", + "ref": ref, + "move_type": "out_invoice", + "narration": self.note, + "currency_id": self.pricelist_id.currency_id.id, + # 'campaign_id': self.campaign_id.id, + # 'medium_id': self.medium_id.id, + # 'source_id': self.source_id.id, + "invoice_user_id": self.user_id and self.user_id.id, + "partner_id": partner_invoice_id, + "partner_bank_id": self.company_id.partner_id.bank_ids[:1].id, + "journal_id": journal.id, # company comes from the journal + "invoice_origin": self.name, + "invoice_payment_term_id": self.payment_term_id.id, + "transaction_ids": [(6, 0, self.transaction_ids.ids)], + "invoice_line_ids": [], + "company_id": self.company_id.id, + "payment_reference": self.name, + "fiscal_position_id": self.env["account.fiscal.position"] + .with_company(self.company_id.id) + ._get_fiscal_position(self.env["res.partner"].browse(partner_invoice_id)) + .id, + } + return invoice_vals + + def do_payment( + self, + journal, + receivable_account, + user, + amount, + folio, + reservations=False, + services=False, + partner=False, + date=False, + pay_type=False, + ref=False, + ): + """ + create folio payment + type: set cash to use statement or bank to use account.payment, + by default, use the journal type + """ + if not pay_type: + pay_type = journal.type + + reference = folio.name + if folio.external_reference: + reference += " - " + folio.external_reference + if ref and not ref in reference: + reference += ": " + ref + vals = { + "journal_id": journal.id, + "partner_id": partner.id if partner else False, + "amount": amount, + "date": date or fields.Date.today(), + "ref": reference, + "folio_ids": [(6, 0, [folio.id])], + "payment_type": "inbound", + "partner_type": "customer", + "state": "draft", + "origin_reference": folio.external_reference, + } + pay = self.env["account.payment"].create(vals) + pay.sudo().message_post_with_view( + "mail.message_origin_link", + values={ + "self": pay, + "origin": folio, + }, + subtype_id=self.env.ref("mail.mt_note").id, + email_from=user.partner_id.email_formatted + or folio.pms_property_id.email_formatted, + ) + + pay.action_post() + + # Review: force to autoreconcile payment with invoices already created + pay.flush() + for move in folio.move_ids: + move._autoreconcile_folio_payments() + + # Automatic register payment in cash register + # TODO: cash_register to avoid flow in the new api (delete it in the future) + if pay_type == "cash" and self.env.context.get("cash_register"): + line = self._get_statement_line_vals( + journal=journal, + receivable_account=receivable_account, + user=user, + amount=amount, + folios=folio, + reservations=reservations, + services=services, + partner=partner, + date=date, + ) + self.env["account.bank.statement.line"].sudo().create(line) + folio.sudo().message_post( + body=_( + """Payment: %s by %s""", + amount, + journal.display_name, + ), + email_from=user.partner_id.email_formatted + or folio.pms_property_id.email_formatted, + ) + for reservation in folio.reservation_ids: + reservation.sudo().message_post( + body=_( + """Payment: %s by %s""", + amount, + journal.display_name, + ), + email_from=user.partner_id.email_formatted + or folio.pms_property_id.email_formatted, + ) + return True + + def do_refund( + self, + journal, + receivable_account, + user, + amount, + folio, + reservations=False, + services=False, + partner=False, + date=False, + pay_type=False, + ref=False, + ): + """ + create folio refund + type: set cash to use statement or bank to use account.payment, + by default, use the journal type + """ + if not pay_type: + pay_type = journal.type + reference = folio.name + if folio.external_reference: + reference += " - " + folio.external_reference + if ref and not ref in reference: + reference += ": " + ref + vals = { + "journal_id": journal.id, + "partner_id": partner.id if partner else False, + "amount": amount if amount > 0 else -amount, + "date": date or fields.Date.today(), + "ref": reference, + "folio_ids": [(6, 0, [folio.id])], + "payment_type": "outbound", + "partner_type": "customer", + "state": "draft", + } + pay = self.env["account.payment"].create(vals) + pay.sudo().message_post_with_view( + "mail.message_origin_link", + values={ + "self": pay, + "origin": folio, + }, + subtype_id=self.env.ref("mail.mt_note").id, + email_from=user.partner_id.email_formatted + or folio.pms_property_id.email_formatted, + ) + pay.action_post() + + # Automatic register refund in cash register + # TODO: cash_register to avoid flow in the new api (delete it in the future) + if pay_type == "cash" and self.env.context.get("cash_register"): + line = self._get_statement_line_vals( + journal=journal, + receivable_account=receivable_account, + user=user, + amount=amount if amount < 0 else -amount, + folios=folio, + reservations=reservations, + services=services, + partner=partner, + date=date, + ) + self.env["account.bank.statement.line"].sudo().create(line) + + folio.sudo().message_post( + body=_( + """Refund: %s by %s""", + amount, + journal.display_name, + ), + email_from=user.partner_id.email_formatted + or folio.pms_property_id.email_formatted, + ) + for reservation in folio.reservation_ids: + reservation.sudo().message_post( + body=_( + """Refund: %s by %s""", + amount, + journal.display_name, + ), + email_from=user.partner_id.email_formatted + or folio.pms_property_id.email_formatted, + ) + return True + + def open_wizard_several_partners(self): + ctx = dict( + folio_id=self.id, + possible_existing_customer_ids=self.possible_existing_customer_ids.ids, + ) + return { + "view_type": "form", + "view_mode": "form", + "name": "Several Customers", + "res_model": "pms.several.partners.wizard", + "target": "new", + "type": "ir.actions.act_window", + "context": ctx, + } + + @api.model + def _get_statement_line_vals( + self, + journal, + receivable_account, + user, + amount, + folios, + reservations=False, + services=False, + partner=False, + date=False, + ): + property_folio_id = folios.mapped("pms_property_id.id") + if len(property_folio_id) != 1: + raise ValidationError(_("Only can payment by property")) + ctx = dict(self.env.context, company_id=folios[0].company_id.id) + if not date: + date = fields.Date.today() + domain = [ + ("journal_id", "=", journal.id), + ("pms_property_id", "=", property_folio_id[0]), + ("state", "=", "open"), + ("date", "=", date), + ] + statement = self.env["account.bank.statement"].sudo().search(domain, limit=1) + reservation_ids = reservations.ids if reservations else [] + service_ids = services.ids if services else [] + if not statement: + # TODO: cash control option + st_values = { + "journal_id": journal.id, + "user_id": self.env.user.id, + "pms_property_id": property_folio_id[0], + "name": datetime.datetime.today().strftime( + get_lang(self.env).date_format + ), + } + statement = ( + self.env["account.bank.statement"] + .with_context(ctx) + .sudo() + .create(st_values) + ) + return { + "date": date, + "amount": amount, + "partner_id": partner.id if partner else False, + "folio_ids": [(6, 0, folios.ids)], + "reservation_ids": [(6, 0, reservation_ids)], + "service_ids": [(6, 0, service_ids)], + "payment_ref": ", ".join(folios.mapped("name")), + "statement_id": statement.id, + "journal_id": statement.journal_id.id, + "counterpart_account_id": receivable_account.id, + } + + @api.model + def _get_reservation_sale_lines(self, folio, reservation, sequence): + sale_reservation_vals = [] + if not reservation.sale_line_ids.filtered(lambda x: x.name == reservation.name): + sale_reservation_vals.append( + ( + 0, + 0, + { + "name": reservation.name, + "display_type": "line_section", + "product_id": False, + "product_uom_qty": 0, + "discount": 0, + "price_unit": 0, + "tax_ids": False, + "folio_id": folio.id, + "reservation_id": reservation.id, + "sequence": sequence, + }, + ) + ) + else: + sequence += 1 + sale_reservation_vals.append( + ( + 1, + reservation.sale_line_ids.filtered( + lambda x: x.name == reservation.name + ).id, + { + "sequence": sequence, + }, + ) + ) + expected_reservation_lines = self.env["pms.reservation.line"].read_group( + [ + ("reservation_id", "=", reservation.id), + ("cancel_discount", "<", 100), + ], + ["price", "discount", "cancel_discount", "default_invoice_to"], + ["price", "discount", "cancel_discount", "default_invoice_to"], + 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): + sequence += 1 + lines_to = self.env["pms.reservation.line"].search(item["__domain"]) + final_discount = self.concat_discounts( + item["discount"], item["cancel_discount"] + ) + partner_invoice = lines_to.mapped("default_invoice_to") + if current_sale_line_ids and index <= (len(current_sale_line_ids) - 1): + current = { + "price_unit": item["price"], + "discount": final_discount, + "reservation_line_ids": [(6, 0, lines_to.ids)], + "sequence": sequence, + "default_invoice_to": partner_invoice[0].id + if partner_invoice + else current_sale_line_ids[index].default_invoice_to, + } + sale_reservation_vals.append( + (1, current_sale_line_ids[index].id, current) + ) + else: + new = { + "reservation_id": reservation.id, + "price_unit": item["price"], + "discount": final_discount, + "folio_id": folio.id, + "product_id": reservation.room_type_id.product_id.id, + "tax_ids": [(6, 0, reservation.tax_ids.ids)], + "reservation_line_ids": [(6, 0, lines_to.ids)], + "sequence": sequence, + "default_invoice_to": partner_invoice[0].id + if partner_invoice + else False, + } + sale_reservation_vals.append((0, 0, new)) + folio_sale_lines_to_remove = [] + 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) + ] + return sale_reservation_vals, folio_sale_lines_to_remove + + @api.model + def _get_service_sale_lines(self, folio, reservation, sequence): + sale_service_vals = [] + folio_sale_lines_to_remove = [] + 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", "default_invoice_to"], + ["price_unit", "discount", "cancel_discount", "default_invoice_to"], + 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"] + ) + partner_invoice = lines_to.mapped("default_invoice_to") + if current_sale_service_ids and index <= ( + len(current_sale_service_ids) - 1 + ): + current = { + "price_unit": item["price_unit"], + "discount": final_discount, + "service_line_ids": [(6, 0, lines_to.ids)], + "sequence": sequence, + "default_invoice_to": partner_invoice[0].id + if partner_invoice + else current_sale_service_ids[index].default_invoice_to, + } + sale_service_vals.append( + (1, current_sale_service_ids[index].id, current) + ) + else: + new = { + "service_id": service.id, + "price_unit": item["price_unit"], + "discount": final_discount, + "folio_id": folio.id, + "reservation_id": reservation.id, + "service_line_ids": [(6, 0, lines_to.ids)], + "product_id": service.product_id.id, + "tax_ids": [(6, 0, service.tax_ids.ids)], + "sequence": sequence, + "default_invoice_to": partner_invoice[0].id + if partner_invoice + else False, + } + sale_service_vals.append((0, 0, new)) + sequence = sequence + 1 + 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) + ] + return sale_service_vals, folio_sale_lines_to_remove + + @api.model + def _get_folio_services_sale_lines(self, folio, sequence): + folio_services = folio.service_ids.filtered(lambda x: not x.reservation_id) + sale_folio_lines = [] + sale_folio_lines_to_remove = [] + if folio_services: + if not folio.sale_line_ids.filtered(lambda x: x.name == _("Others")): + folio.sale_line_ids = [ + ( + 0, + False, + { + "display_type": "line_section", + "product_id": False, + "product_uom_qty": 0, + "discount": 0, + "price_unit": 0, + "tax_ids": False, + "name": _("Others"), + "sequence": sequence, + }, + ) + ] + for folio_service in folio_services: + sequence += 1 + 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, + ) + 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"] + ) + if current_folio_service_ids and index <= ( + len(current_folio_service_ids) - 1 + ): + current = { + "price_unit": item["price_unit"], + "discount": final_discount, + "service_line_ids": [(6, 0, lines_to.ids)], + "sequence": sequence, + } + sale_folio_lines.append( + (1, current_folio_service_ids[index].id, current) + ) + 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)], + "product_id": folio_service.product_id.id, + "tax_ids": [(6, 0, folio_service.tax_ids.ids)], + "sequence": sequence, + } + sale_folio_lines.append((0, 0, new)) + if len(expected_folio_services) < len(current_folio_service_ids): + sale_folio_lines_to_remove = [ + value.id + for index, value in enumerate(current_folio_service_ids) + if index > (len(expected_folio_services) - 1) + ] + else: + sale_folio_lines_to_remove = folio.sale_line_ids.filtered( + lambda x: x.name == _("Others") + ) + return sale_folio_lines, sale_folio_lines_to_remove + + @api.model + def _prepare_down_payment_section_line(self, **optional_values): + """ + Prepare the dict of values to create a new down + payment section for a sales order line. + :param optional_values: any parameter that should + be added to the returned down payment section + """ + down_payments_section_line = { + "display_type": "line_section", + "name": _("Down Payments"), + "product_id": False, + "product_uom_id": False, + "quantity": 0, + "discount": 0, + "price_unit": 0, + "account_id": False, + } + if optional_values: + down_payments_section_line.update(optional_values) + return down_payments_section_line + + @api.model + def _nothing_to_invoice_error(self): + msg = _( + """There is nothing to invoice!\n + Reason(s) of this behavior could be: + - You should deliver your products before invoicing them: Click on the "truck" + icon (top-right of your screen) and follow instructions. + - You should modify the invoicing policy of your product: Open the product, + go to the "Sales tab" and modify invoicing policy from "delivered quantities" + to "ordered quantities". + """ + ) + return UserError(msg) + + @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.model + def _apply_mobile(self, record): + if record.partner_id and not record.mobile: + record.mobile = record.partner_id.mobile + elif not record.mobile: + record.mobile = False + + @api.model + def _apply_email(self, record): + if record.partner_id and not record.email: + record.email = record.partner_id.email + elif not record.email: + record.email = False + + @api.model + def _apply_possible_existing_customer_ids( + self, email=False, mobile=False, partner=False + ): + possible_customer = False + if email and not partner: + possible_customer = self.env["res.partner"].search([("email", "=", email)]) + if mobile and not partner: + possible_customer = self.env["res.partner"].search( + [("mobile", "=", mobile)] + ) + return possible_customer + + def _create_payment_transaction(self, vals): + # Ensure the currencies are the same. + currency = self[0].currency_id + if any(folio.currency_id != currency for folio in self): + raise ValidationError( + _( + "A transaction can't be linked to folios having different currencies." + ) + ) + + # Ensure the partner are the same. + partner = self[0].partner_id + if any(folio.partner_id != partner for folio in self): + raise ValidationError( + _("A transaction can't be linked to folios having different partners.") + ) + + # Try to retrieve the acquirer. However, fallback to the token's acquirer. + acquirer_id = vals.get("acquirer_id") + acquirer = None + payment_token_id = vals.get("payment_token_id") + + if payment_token_id: + payment_token = self.env["payment.token"].sudo().browse(payment_token_id) + + # Check payment_token/acquirer matching or take the acquirer from token + if acquirer_id: + acquirer = self.env["payment.provider"].browse(acquirer_id) + if payment_token and payment_token.acquirer_id != acquirer: + raise ValidationError( + _("Invalid token found! Token acquirer %s != %s") + % (payment_token.acquirer_id.name, acquirer.name) + ) + if payment_token and payment_token.partner_id != partner: + raise ValidationError( + _("Invalid token found! Token partner %s != %s") + % (payment_token.partner.name, partner.name) + ) + else: + acquirer = payment_token.acquirer_id + + # Check an acquirer is there. + if not acquirer_id and not acquirer: + raise ValidationError( + _("A payment acquirer is required to create a transaction.") + ) + + if not acquirer: + acquirer = self.env["payment.provider"].browse(acquirer_id) + + # Check a journal is set on acquirer. + if not acquirer.journal_id: + raise ValidationError( + _("A journal must be specified for the acquirer %s.", acquirer.name) + ) + + if not acquirer_id and acquirer: + vals["acquirer_id"] = acquirer.id + + vals.update( + { + "amount": sum(self.mapped("amount_total")), + "currency_id": currency.id, + "partner_id": partner.id, + "folio_ids": [(6, 0, self.ids)], + } + ) + transaction = self.env["payment.transaction"].create(vals) + + # Process directly if payment_token + if transaction.payment_token_id: + transaction.s2s_do_transaction() + + return transaction diff --git a/pms/models/pms_property.py b/pms/models/pms_property.py new file mode 100644 index 0000000000..1b0c055cb2 --- /dev/null +++ b/pms/models/pms_property.py @@ -0,0 +1,1010 @@ +# 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(), + ) + + @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 diff --git a/pms/models/pms_reservation.py b/pms/models/pms_reservation.py new file mode 100644 index 0000000000..2626d61ede --- /dev/null +++ b/pms/models/pms_reservation.py @@ -0,0 +1,2573 @@ +# Copyright 2017-2018 Alexandre Díaz +# Copyright 2017 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import datetime +import logging +import time + +from odoo import _, api, fields, models +from odoo.exceptions import UserError, ValidationError + +_logger = logging.getLogger(__name__) + + +class PmsReservation(models.Model): + _name = "pms.reservation" + _description = "Reservation" + _inherit = ["mail.thread", "mail.activity.mixin", "portal.mixin"] + _order = "write_date desc, create_date desc" + # TODO: + # consider near_to_checkin & pending_notifications to order + _check_pms_properties_auto = True + _check_company_auto = True + + name = fields.Text( + string="Reservation Code", + help="Reservation Code Identification", + readonly=True, + ) + external_reference = fields.Char( + string="External Reference", + help="Reference of this folio in an external system", + compute="_compute_external_reference", + store=True, + readonly=False, + ) + folio_sequence = fields.Integer( + string="Folio Sequence", + help="Techinal field to get reservation name", + readonly=True, + ) + priority = fields.Integer( + string="Priority", + help="Priority of a reservation", + store="True", + compute="_compute_priority", + ) + preferred_room_id = fields.Many2one( + string="Room", + help="It's the preferred room assigned to reservation, " + "empty if reservation is splitted", + copy=False, + comodel_name="pms.room", + ondelete="restrict", + index=True, + domain="[" + "('id', 'in', allowed_room_ids)," + "('pms_property_id', '=', pms_property_id)," + "]", + tracking=True, + check_pms_properties=True, + ) + allowed_room_ids = fields.Many2many( + string="Allowed Rooms", + help="It contains all available rooms for this reservation", + comodel_name="pms.room", + compute="_compute_allowed_room_ids", + ) + folio_id = fields.Many2one( + string="Folio", + help="The folio where the reservations are included", + copy=False, + index=True, + comodel_name="pms.folio", + ondelete="restrict", + tracking=True, + check_company=True, + ) + sale_line_ids = fields.One2many( + comodel_name="folio.sale.line", + inverse_name="reservation_id", + string="Sale Lines", + copy=False, + ) + board_service_room_id = fields.Many2one( + string="Board Service", + help="Board Service included in the room", + readonly=False, + store=True, + comodel_name="pms.board.service.room.type", + compute="_compute_board_service_room_id", + index=True, + tracking=True, + check_pms_properties=True, + ) + room_type_id = fields.Many2one( + string="Room Type", + help="Room Type sold on the reservation," + "it doesn't necessarily correspond to" + " the room actually assigned", + readonly=False, + copy=False, + store=True, + index=True, + comodel_name="pms.room.type", + ondelete="restrict", + compute="_compute_room_type_id", + tracking=True, + check_pms_properties=True, + ) + partner_id = fields.Many2one( + string="Customer", + help="Name of who made the reservation", + readonly=False, + store=True, + index=True, + comodel_name="res.partner", + ondelete="restrict", + compute="_compute_partner_id", + tracking=True, + check_pms_properties=True, + ) + agency_id = fields.Many2one( + string="Agency", + help="Agency that made the reservation", + readonly=False, + store=True, + index=True, + related="folio_id.agency_id", + depends=["folio_id.agency_id"], + tracking=True, + ) + sale_channel_ids = fields.Many2many( + string="Sale Channels", + help="Sale Channels through which reservation lines were managed", + store=True, + compute="_compute_sale_channel_ids", + comodel_name="pms.sale.channel", + ) + sale_channel_origin_id = fields.Many2one( + string="Sale Channel Origin", + help="Sale Channel through which reservation was created, the original", + default=lambda self: self._get_default_sale_channel_origin(), + comodel_name="pms.sale.channel", + index=True, + ) + force_update_origin = fields.Boolean( + string="Update Sale Channel Origin", + help="This field is for force update in sale channel " + "origin of folio and another reservations", + store=True, + readonly=False, + compute="_compute_force_update_origin", + ) + is_origin_channel_check_visible = fields.Boolean( + string="Check force update origin visible", + help="Technical field to make visible update " "origin channel check", + store=True, + readonly=False, + compute="_compute_is_origin_channel_check_visible", + ) + closure_reason_id = fields.Many2one( + string="Closure Reason", + help="Reason why the reservation cannot be made", + related="folio_id.closure_reason_id", + check_pms_properties=True, + readonly=False, + ) + out_service_description = fields.Text( + string="Cause of out of service", + help="Indicates the cause of out of service", + related="folio_id.out_service_description", + readonly=False, + ) + company_id = fields.Many2one( + string="Company", + help="Company to which the reservation belongs", + readonly=True, + store=True, + related="folio_id.company_id", + index=True, + ) + pms_property_id = fields.Many2one( + string="Pms Property", + help="Property to which the reservation belongs", + store=True, + readonly=False, + default=lambda self: self.env.user.get_active_property_ids()[0], + related="folio_id.pms_property_id", + comodel_name="pms.property", + index=True, + check_pms_properties=True, + ) + reservation_line_ids = fields.One2many( + string="Reservation Lines", + help="They are the lines of the reservation into a reservation," + "they corresponds to the nights", + readonly=False, + copy=False, + store=True, + compute="_compute_reservation_line_ids", + comodel_name="pms.reservation.line", + inverse_name="reservation_id", + check_pms_properties=True, + ) + service_ids = fields.One2many( + string="Services", + help="Included services in the reservation", + readonly=False, + store=True, + comodel_name="pms.service", + inverse_name="reservation_id", + compute="_compute_service_ids", + # check_company=True, + check_pms_properties=True, + ) + pricelist_id = fields.Many2one( + string="Pricelist", + help="Pricelist that guides the prices of the reservation", + readonly=False, + store=True, + comodel_name="product.pricelist", + ondelete="restrict", + compute="_compute_pricelist_id", + tracking=True, + check_pms_properties=True, + index=True, + domain="[('is_pms_available', '=', True)]", + ) + user_id = fields.Many2one( + string="Reception Manager", + help="The reception manager in the folio", + readonly=False, + index=True, + store=True, + comodel_name="res.users", + ondelete="restrict", + compute="_compute_user_id", + tracking=True, + ) + show_update_pricelist = fields.Boolean( + string="Has Pricelist Changed", + help="Technical Field, True if the pricelist was changed;\n" + " this will then display a recomputation button", + store=True, + compute="_compute_show_update_pricelist", + ) + commission_percent = fields.Float( + string="Commission percent (%)", + help="Percentage corresponding to commission", + readonly=False, + store=True, + compute="_compute_commission_percent", + tracking=True, + ) + commission_amount = fields.Float( + string="Commission amount", + help="Amount corresponding to commission", + store=True, + compute="_compute_commission_amount", + ) + checkin_partner_ids = fields.One2many( + string="Checkin Partners", + help="Guests who will occupy the room", + readonly=False, + copy=False, + store=True, + compute="_compute_checkin_partner_ids", + comodel_name="pms.checkin.partner", + inverse_name="reservation_id", + check_pms_properties=True, + ) + count_pending_arrival = fields.Integer( + string="Pending Arrival", + help="Number of guest with pending checkin", + store=True, + compute="_compute_count_pending_arrival", + ) + checkins_ratio = fields.Integer( + string="Pending Arrival Ratio", + help="Proportion of guest pending checkin", + compute="_compute_checkins_ratio", + ) + pending_checkin_data = fields.Integer( + string="Checkin Data", + help="Data missing at checkin", + store=True, + compute="_compute_pending_checkin_data", + ) + ratio_checkin_data = fields.Integer( + string="Complete cardex", + help="Proportion of guest data complete at checkin", + compute="_compute_ratio_checkin_data", + ) + ready_for_checkin = fields.Boolean( + string="Ready for checkin", + help="Indicates the reservations with checkin_partner data enought to checkin", + compute="_compute_ready_for_checkin", + ) + allowed_checkin = fields.Boolean( + string="Allowed checkin", + help="Technical field, Indicates if there isn't a checkin_partner data" + "Only can be true if checkin is today or was in the past", + compute="_compute_allowed_checkin", + search="_search_allowed_checkin", + ) + + allowed_checkout = fields.Boolean( + string="Allowed checkout", + help="Technical field, Indicates that reservation is ready for checkout" + "only can be true if reservation state is 'onboard' or departure_delayed" + "and checkout is today or will be in the future", + compute="_compute_allowed_checkout", + search="_search_allowed_checkout", + ) + + allowed_cancel = fields.Boolean( + string="Allowed cancel", + help="Technical field, Indicates that reservation can be cancelled," + "that happened when state is 'cancel', 'done', or 'departure_delayed'", + compute="_compute_allowed_cancel", + search="_search_allowed_cancel", + ) + + segmentation_ids = fields.Many2many( + string="Segmentation", + help="Segmentation tags to classify reservations", + default=lambda self: self._get_default_segmentation(), + comodel_name="res.partner.category", + ondelete="restrict", + domain="[('is_used_in_checkin', '=', True)]", + ) + currency_id = fields.Many2one( + string="Currency", + help="The currency used in relation to the pricelist", + readonly=True, + store=True, + index=True, + related="pricelist_id.currency_id", + depends=["pricelist_id"], + ) + tax_ids = fields.Many2many( + string="Taxes", + help="Taxes applied in the reservation", + readonly=False, + store=True, + compute="_compute_tax_ids", + comodel_name="account.tax", + domain=["|", ("active", "=", False), ("active", "=", True)], + ) + adults = fields.Integer( + string="Adults", + help="List of adults there in guest list", + readonly=False, + store=True, + compute="_compute_adults", + tracking=True, + ) + children_occupying = fields.Integer( + string="Children occupying", + help="Number of children there in guest list whose presence counts", + ) + children = fields.Integer( + string="Children", + help="Number total of children there in guest list," + "whose presence counts or not", + readonly=False, + tracking=True, + ) + to_assign = fields.Boolean( + string="To Assign", + help="It is True if the room of the reservation has been assigned " + "automatically, False if it was confirmed by a person in charge", + default=True, + ) + state = fields.Selection( + string="State", + help="The state of the reservation. " + "It can be 'Pre-reservation', 'Pending arrival', 'On Board', 'Out', " + "'Cancelled', 'Arrival Delayed' or 'Departure Delayed'", + readonly=True, + index=True, + default=lambda *a: "draft", + copy=False, + selection=[ + ("draft", "Pre-reservation"), + ("confirm", "Pending arrival"), + ("onboard", "On Board"), + ("done", "Out"), + ("cancel", "Cancelled"), + ("arrival_delayed", "Arrival Delayed"), + ("departure_delayed", "Departure delayed"), + ], + tracking=True, + ) + cancel_datetime = fields.Datetime( + string="Cancel Date", + help="Date when the reservation was cancelled", + readonly=True, + copy=False, + ) + reservation_type = fields.Selection( + string="Reservation Type", + help="Type of reservations. It can be 'normal', 'staff' or 'out of service", + store=True, + readonly=False, + compute="_compute_reservation_type", + selection=[("normal", "Normal"), ("staff", "Staff"), ("out", "Out of Service")], + ) + splitted = fields.Boolean( + string="Splitted", + help="Field that indicates if the reservation is split. " + "A reservation is split when guests don't sleep in the same room every night", + store=True, + compute="_compute_splitted", + ) + rooms = fields.Char( + string="Room/s", + help="Rooms that are reserved", + compute="_compute_rooms", + store=True, + tracking=True, + ) + credit_card_details = fields.Text( + string="Credit Card Details", help="", related="folio_id.credit_card_details" + ) + cancelled_reason = fields.Selection( + string="Reason of cancellation", + help="Field indicating type of cancellation. " + "It can be 'late', 'intime' or 'noshow'", + copy=False, + store=True, + selection=[ + ("late", "Late"), + ("intime", "In time"), + ("noshow", "No Show"), + ("modified", "Modified"), + ], + tracking=True, + ) + + checkin = fields.Date( + string="Check In", + help="It is the checkin date of the reservation, ", + compute="_compute_checkin", + readonly=False, + store=True, + copy=False, + tracking=True, + ) + checkout = fields.Date( + string="Check Out", + help="It is the checkout date of the reservation, ", + compute="_compute_checkout", + readonly=False, + store=True, + copy=False, + tracking=True, + ) + arrival_hour = fields.Char( + string="Arrival Hour", + help="Arrival Hour (HH:MM)", + readonly=False, + store=True, + compute="_compute_arrival_hour", + ) + departure_hour = fields.Char( + string="Departure Hour", + help="Departure Hour (HH:MM)", + readonly=False, + store=True, + compute="_compute_departure_hour", + ) + checkin_datetime = fields.Datetime( + string="Exact Arrival", + help="This field is the day and time of arrival of the reservation." + "It is formed with the checkin and arrival_hour fields", + compute="_compute_checkin_datetime", + ) + checkout_datetime = fields.Datetime( + string="Exact Departure", + help="This field is the day and time of departure of the reservation." + "It is formed with the checkout and departure_hour fields", + compute="_compute_checkout_datetime", + ) + checkin_partner_count = fields.Integer( + string="Checkin counter", + help="Number of checkin partners in a reservation", + compute="_compute_checkin_partner_count", + ) + checkin_partner_pending_count = fields.Integer( + string="Checkin Pending Num", + help="Number of checkin partners pending to checkin in a reservation", + compute="_compute_checkin_partner_count", + search="_search_checkin_partner_pending", + ) + overbooking = fields.Boolean( + string="Is Overbooking", + help="Indicate if exists overbooking", + compute="_compute_overbooking", + store=True, + ) + nights = fields.Integer( + string="Nights", + help="Number of nights of a reservation", + compute="_compute_nights", + store=True, + ) + folio_pending_amount = fields.Monetary( + string="Pending Amount", + help="The amount that remains to be paid from folio", + related="folio_id.pending_amount", + tracking=True, + ) + folio_payment_state = fields.Selection( + string="Payment State", + help="The status of the folio payment", + store=True, + related="folio_id.payment_state", + tracking=True, + ) + shared_folio = fields.Boolean( + string="Shared Folio", + help="Used to notify is the reservation folio has other reservations/services", + compute="_compute_shared_folio", + ) + partner_name = fields.Char( + string="Customer Name", + help="To whom the room is assigned", + store=True, + readonly=False, + compute="_compute_partner_name", + ) + email = fields.Char( + string="E-mail", + help="Customer E-mail", + store=True, + readonly=False, + compute="_compute_email", + ) + mobile = fields.Char( + string="Mobile", + help="Customer Mobile", + store=True, + readonly=False, + compute="_compute_mobile", + ) + partner_internal_comment = fields.Text( + string="Internal Partner Notes", + help="Internal notes of the partner", + related="partner_id.comment", + store=True, + readonly=False, + ) + partner_incongruences = fields.Char( + string="partner_incongruences", + help="indicates that some partner fields \ + on the reservation do not correspond to that of \ + the associated partner", + compute="_compute_partner_incongruences", + ) + partner_requests = fields.Text( + string="Partner Requests", + help="Guest requests", + ) + folio_internal_comment = fields.Text( + string="Internal Folio Notes", + help="Internal comment for folio", + related="folio_id.internal_comment", + store=True, + readonly=False, + ) + preconfirm = fields.Boolean( + string="Auto confirm to Save", + help="Technical field that indicates the reservation is not comfirm yet", + default=True, + ) + invoice_status = fields.Selection( + string="Invoice Status", + help="The status of the invoices in folio. Can be 'invoiced'," + " 'to_invoice' or 'no'.", + store=True, + readonly=True, + selection=[ + ("invoiced", "Fully Invoiced"), + ("to_invoice", "To Invoice"), + ("no", "Nothing to Invoice"), + ], + compute="_compute_invoice_status", + ) + # analytic_tag_ids = fields.Many2many( + # string="Analytic Tags", + # comodel_name="account.analytic.tag", + # relation="pms_reservation_account_analytic_tag", + # column1="reservation_id", + # column2="account_analytic_tag_id", + # domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", + # ) + analytic_line_ids = fields.One2many( + string="Analytic lines", + comodel_name="account.analytic.line", + inverse_name="so_line", + ) + price_subtotal = fields.Monetary( + string="Subtotal", + help="Subtotal price without taxes", + readonly=True, + store=True, + compute="_compute_amount_reservation", + ) + price_total = fields.Monetary( + string="Total", + help="Total price with taxes", + readonly=True, + store=True, + compute="_compute_amount_reservation", + tracking=True, + ) + price_tax = fields.Float( + string="Taxes Amount", + help="Total of taxes in a reservation", + readonly=True, + store=True, + compute="_compute_amount_reservation", + ) + price_services = fields.Monetary( + string="Services Total", + help="Total price from services of a reservation", + readonly=True, + store=True, + compute="_compute_price_services", + ) + price_room_services_set = fields.Monetary( + string="Room Services Total", + help="Total price of room and services", + readonly=True, + store=True, + compute="_compute_price_room_services_set", + ) + discount = fields.Float( + string="Discount (€)", + help="Discount of total price in reservation", + readonly=False, + store=True, + digits=("Discount"), + compute="_compute_discount", + tracking=True, + ) + + services_discount = fields.Float( + string="Services discount (€)", + help="Services discount", + readonly=False, + store=True, + digits=("Discount"), + compute="_compute_services_discount", + tracking=True, + ) + date_order = fields.Datetime( + string="Date Order", + help="Order date of reservation", + default=fields.Datetime.now, + store=True, + readonly=False, + ) + + check_adults = fields.Boolean( + help="Internal field to force room capacity validations", + compute="_compute_check_adults", + readonly=False, + store=True, + ) + + possible_existing_customer_ids = fields.One2many( + string="Possible existing customer", + compute="_compute_possible_existing_customer_ids", + comodel_name="res.partner", + inverse_name="reservation_possible_customer_id", + ) + + avoid_mails = fields.Boolean( + string="Avoid comunication mails", + help="Field to indicate not sent mail comunications", + compute="_compute_avoid_mails", + readonly=False, + store=True, + ) + + to_send_confirmation_mail = fields.Boolean( + string="To Send Confirmation Mail", + compute="_compute_to_send_confirmation_mail", + readonly=False, + store=True, + ) + + to_send_modification_mail = fields.Boolean( + string="To Send Modification Mail", + compute="_compute_to_send_modification_mail", + readonly=False, + store=True, + ) + + to_send_exit_mail = fields.Boolean( + string="To Send Exit Mail", + compute="_compute_to_send_exit_mail", + readonly=False, + store=True, + ) + + to_send_cancelation_mail = fields.Boolean( + string="To Send Cancelation Mail", + compute="_compute_to_send_cancelation_mail", + readonly=False, + store=True, + ) + + overnight_room = fields.Boolean( + related="room_type_id.overnight_room", + store=True, + ) + # TODO: This field is deprecated + lang = fields.Many2one( + string="Language", comodel_name="res.lang", compute="_compute_lang" + ) + blocked = fields.Boolean( + string="Blocked", + help="Indicates if the reservation is blocked", + default=False, + ) + + is_reselling = fields.Boolean( + string="Reselling", + help="Indicate if exists reselling in any reservation line", + compute="_compute_is_reselling", + store=True, + readonly=False, + ) + count_alternative_free_rooms = fields.Integer( + string="Count alternative free rooms", + compute="_compute_count_alternative_free_rooms", + ) + + @api.depends("folio_id", "folio_id.external_reference") + def _compute_external_reference(self): + for reservation in self: + if not reservation.external_reference: + reservation.external_reference = ( + reservation._get_reservation_external_reference() + ) + + def _get_reservation_external_reference(self): + self.ensure_one() + folio = self.folio_id + if folio and folio.external_reference: + return folio.external_reference + else: + return False + + def _compute_date_order(self): + for record in self: + record.date_order = datetime.datetime.today() + + @api.depends( + "service_ids", + "service_ids.service_line_ids", + "service_ids.service_line_ids.product_id", + "service_ids.service_line_ids.day_qty", + "reservation_line_ids", + "reservation_line_ids.room_id", + ) + def _compute_check_adults(self): + for record in self: + record.check_adults = True + + @api.depends( + "checkin", + "checkout", + "state", + "folio_payment_state", + "to_assign", + ) + def _compute_priority(self): + # TODO: Notifications priority + for record in self: + if record.to_assign or record.state in ( + "arrival_delayed", + "departure_delayed", + ): + record.priority = 1 + elif record.state == "cancel": + record.priority = record.cancel_priority() + elif record.state == "onboard": + record.priority = record.onboard_priority() + elif record.state in ("draf", "confirm"): + record.priority = record.reservations_future_priority() + elif record.state == "done": + record.priority = record.reservations_past_priority() + + def cancel_priority(self): + self.ensure_one() + if self.folio_pending_amount > 0: + return 2 + elif self.checkout >= fields.date.today(): + return 100 + else: + return 1000 * (fields.date.today() - self.checkout).days + + def onboard_priority(self): + self.ensure_one() + days_for_checkout = (self.checkout - fields.date.today()).days + if self.folio_pending_amount > 0: + return days_for_checkout + else: + return 3 * days_for_checkout + + def reservations_future_priority(self): + self.ensure_one() + days_for_checkin = (self.checkin - fields.date.today()).days + if days_for_checkin < 3: + return 2 * days_for_checkin + elif days_for_checkin < 20: + return 3 * days_for_checkin + else: + return 4 * days_for_checkin + + def reservations_past_priority(self): + self.ensure_one() + if self.folio_pending_amount > 0: + return 3 + days_from_checkout = (fields.date.today() - self.checkout).days + if days_from_checkout <= 1: + return 6 + elif days_from_checkout < 15: + return 5 * days_from_checkout + elif days_from_checkout <= 90: + return 10 * days_from_checkout + elif days_from_checkout > 90: + return 100 * days_from_checkout + + @api.depends("pricelist_id", "room_type_id") + def _compute_board_service_room_id(self): + for reservation in self: + if reservation.pricelist_id and reservation.room_type_id: + board_service_default = self.env["pms.board.service.room.type"].search( + [ + ("pms_room_type_id", "=", reservation.room_type_id.id), + ("by_default", "=", True), + ("pms_property_id", "=", reservation.pms_property_id.id), + ] + ) + if ( + not reservation.board_service_room_id + or not reservation.board_service_room_id.pms_room_type_id + == reservation.room_type_id + ): + reservation.board_service_room_id = ( + board_service_default.id if board_service_default else False + ) + elif not reservation.board_service_room_id: + reservation.board_service_room_id = False + + @api.depends("preferred_room_id") + def _compute_room_type_id(self): + """ + This method set False to_assign when the user + directly chooses the preferred_room_id, + otherwise, action_assign will be used when the user manually confirms + or changes the preferred_room_id of the reservation + """ + for reservation in self: + if reservation.preferred_room_id and not reservation.room_type_id: + reservation.room_type_id = reservation.preferred_room_id.room_type_id.id + elif not reservation.room_type_id: + reservation.room_type_id = False + + @api.depends("checkin", "arrival_hour") + def _compute_checkin_datetime(self): + for reservation in self: + checkin_hour = int(reservation.arrival_hour[0:2]) + checkin_minut = int(reservation.arrival_hour[3:5]) + checkin_time = datetime.time(checkin_hour, checkin_minut) + checkin_datetime = datetime.datetime.combine( + reservation.checkin, checkin_time + ) + reservation.checkin_datetime = ( + reservation.pms_property_id.date_property_timezone(checkin_datetime) + ) + + @api.depends("checkout", "departure_hour") + def _compute_checkout_datetime(self): + for reservation in self: + checkout_hour = int(reservation.departure_hour[0:2]) + checkout_minut = int(reservation.departure_hour[3:5]) + checkout_time = datetime.time(checkout_hour, checkout_minut) + checkout_datetime = datetime.datetime.combine( + reservation.checkout, checkout_time + ) + reservation.checkout_datetime = ( + reservation.pms_property_id.date_property_timezone(checkout_datetime) + ) + + @api.depends( + "reservation_line_ids.date", + "reservation_line_ids.room_id", + "reservation_line_ids.occupies_availability", + "preferred_room_id", + "room_type_id", + "pricelist_id", + "pms_property_id", + ) + def _compute_allowed_room_ids(self): + for reservation in self: + if reservation.checkin and reservation.checkout: + if reservation.overbooking or reservation.state in ("cancel"): + reservation.allowed_room_ids = self.env["pms.room"].search( + [ + ("active", "=", True), + ] + ) + return + pms_property = reservation.pms_property_id + pms_property = pms_property.with_context( + checkin=reservation.checkin, + checkout=reservation.checkout, + room_type_id=False, # Allows to choose any available room + current_lines=reservation.reservation_line_ids.ids, + pricelist_id=reservation.pricelist_id.id, + class_id=reservation.room_type_id.class_id.id + if reservation.room_type_id + else False, + real_avail=True, + ) + reservation.allowed_room_ids = pms_property.free_room_ids + else: + reservation.allowed_room_ids = False + + @api.depends( + "reservation_type", + "folio_id", + "folio_id.agency_id", + "partner_name", + "email", + "mobile", + ) + def _compute_partner_id(self): + for reservation in self: + if not reservation.partner_id: + if reservation.reservation_type == "out": + reservation.partner_id = False + elif reservation.folio_id and reservation.folio_id.partner_id: + reservation.partner_id = reservation.folio_id.partner_id + elif not reservation.partner_id: + reservation.partner_id = False + + @api.depends("checkin", "checkout") + def _compute_reservation_line_ids(self): + for reservation in self: + cmds = [] + if reservation.checkout and reservation.checkin: + days_diff = (reservation.checkout - reservation.checkin).days + for i in range(0, days_diff): + idate = reservation.checkin + datetime.timedelta(days=i) + old_line = reservation.reservation_line_ids.filtered( + lambda r: r.date == idate + ) + if not old_line: + cmds.append( + ( + 0, + False, + {"date": idate}, + ) + ) + reservation.reservation_line_ids -= ( + reservation.reservation_line_ids.filtered_domain( + [ + "|", + ("date", ">=", reservation.checkout), + ("date", "<", reservation.checkin), + ] + ) + ) + reservation.reservation_line_ids = cmds + else: + if not reservation.reservation_line_ids: + reservation.reservation_line_ids = False + reservation.check_in_out_dates() + + @api.depends("board_service_room_id") + def _compute_service_ids(self): + if self.env.context.get("skip_compute_service_ids", False): + return + for reservation in self: + board_services = [] + old_board_lines = reservation.service_ids.filtered_domain( + [ + ("is_board_service", "=", True), + ] + ) + # Avoid recalculating services if the boardservice has not changed + if ( + old_board_lines + and reservation.board_service_room_id + == reservation._origin.board_service_room_id + ): + return + if reservation.board_service_room_id: + board = self.env["pms.board.service.room.type"].browse( + reservation.board_service_room_id.id + ) + for line in board.board_service_line_ids: + if (reservation.adults and line.adults) or ( + reservation.children and line.children + ): + data = { + "product_id": line.product_id.id, + "is_board_service": True, + "folio_id": reservation.folio_id.id, + "reservation_id": reservation.id, + "board_service_line_id": line.id, + } + board_services.append((0, False, data)) + reservation.service_ids -= old_board_lines + reservation.service_ids = board_services + elif old_board_lines: + reservation.service_ids -= old_board_lines + + @api.depends("partner_id", "agency_id") + def _compute_pricelist_id(self): + for reservation in self: + is_new = not reservation.pricelist_id or isinstance( + reservation.id, models.NewId + ) + if reservation.reservation_type == "out": + reservation.pricelist_id = False + elif ( + is_new + and reservation.agency_id + and reservation.agency_id.apply_pricelist + ): + reservation.pricelist_id = ( + reservation.agency_id.property_product_pricelist + ) + # only change de pricelist if the reservation is not yet saved + # and the partner has a pricelist default + elif ( + is_new + and reservation.partner_id + and reservation.partner_id.property_product_pricelist + and reservation.partner_id.property_product_pricelist.is_pms_available + ): + reservation.pricelist_id = ( + reservation.partner_id.property_product_pricelist + ) + elif not reservation.pricelist_id.id: + if reservation.folio_id and reservation.folio_id.pricelist_id: + reservation.pricelist_id = reservation.folio_id.pricelist_id + else: + reservation.pricelist_id = ( + reservation.pms_property_id.default_pricelist_id + ) + + @api.depends("folio_id", "pms_property_id") + def _compute_user_id(self): + active_user_id = self.env.uid + for res in self: + if not res.user_id and not res.folio_id: + property_users = res.pms_property_id.member_ids.filtered( + lambda u: u.pms_role == "reception" + ).mapped("user_id") + if property_users: + if active_user_id in property_users.ids: + res.user_id = active_user_id + elif property_users: + res.user_id = property_users[0] + else: + res.user_id = active_user_id or res.pms_property_id.user_id + + @api.depends("pricelist_id", "room_type_id") + def _compute_show_update_pricelist(self): + for reservation in self: + if ( + sum(reservation.reservation_line_ids.mapped("price")) > 0 + and ( + reservation.pricelist_id + and reservation._origin.pricelist_id != reservation.pricelist_id + ) + or ( + reservation.room_type_id + and reservation._origin.room_type_id != reservation.room_type_id + ) + ): + reservation.show_update_pricelist = True + else: + reservation.show_update_pricelist = False + + @api.depends("adults") + def _compute_checkin_partner_ids(self): + for reservation in self: + adults = reservation.adults if reservation.reservation_type != "out" else 0 + assigned_checkins = reservation.checkin_partner_ids.filtered( + lambda c: c.state in ("precheckin", "onboard", "done") + ) + unassigned_checkins = reservation.checkin_partner_ids.filtered( + lambda c: c.state in ("dummy", "draft") + ) + leftover_unassigneds_count = ( + len(assigned_checkins) + len(unassigned_checkins) - adults + ) + if len(assigned_checkins) > adults: + raise UserError( + _("Remove some of the leftover assigned checkins first") + ) + elif leftover_unassigneds_count > 0: + for i in range(0, leftover_unassigneds_count): + reservation.checkin_partner_ids = [(2, unassigned_checkins[i].id)] + elif adults > len(reservation.checkin_partner_ids): + checkins_lst = [] + count_new_checkins = adults - len(reservation.checkin_partner_ids) + for _i in range(0, count_new_checkins): + checkins_lst.append( + ( + 0, + False, + { + "reservation_id": reservation.id, + }, + ) + ) + reservation.checkin_partner_ids = checkins_lst + elif adults == 0: + reservation.checkin_partner_ids = False + + @api.depends("checkin_partner_ids", "checkin_partner_ids.state") + def _compute_count_pending_arrival(self): + for reservation in self: + reservation.count_pending_arrival = len( + reservation.checkin_partner_ids.filtered( + lambda c: c.state in ("dummy", "draft", "precheckin") + ) + ) + + @api.depends("count_pending_arrival") + def _compute_checkins_ratio(self): + self.checkins_ratio = 0 + for reservation in self.filtered(lambda r: r.adults > 0): + reservation.checkins_ratio = ( + (reservation.adults - reservation.count_pending_arrival) + * 100 + / reservation.adults + ) + + @api.depends("checkin_partner_ids", "checkin_partner_ids.state") + def _compute_pending_checkin_data(self): + for reservation in self: + reservation.pending_checkin_data = len( + reservation.checkin_partner_ids.filtered( + lambda c: c.state in ("dummy", "draft") + ) + ) + + @api.depends("pending_checkin_data") + def _compute_ratio_checkin_data(self): + self.ratio_checkin_data = 0 + for reservation in self.filtered( + lambda r: r.adults > 0 and r.state != "cancel" + ): + reservation.ratio_checkin_data = ( + (reservation.adults - reservation.pending_checkin_data) + * 100 + / reservation.adults + ) + + def _compute_allowed_checkin(self): + # Reservations still pending entry today + for record in self: + record.allowed_checkin = ( + True + if ( + record.reservation_type != "out" + and record.overnight_room + and record.state in ("draft", "confirm", "arrival_delayed") + and fields.Date.today() >= record.checkin + ) + else False + ) + + def _compute_allowed_checkout(self): + # Reservations still pending checkout today + for record in self: + record.allowed_checkout = ( + True + if ( + record.state in ["onboard", "departure_delayed"] + and fields.Date.today() >= record.checkout + ) + else False + ) + + def _compute_allowed_cancel(self): + # Reservations can be cancelled + for record in self: + record.allowed_cancel = ( + True + if ( + record.state not in ["done"] + and fields.Date.today() <= record.checkout + ) + else False + ) + + def _compute_ready_for_checkin(self): + # Reservations with hosts data enought to checkin + for record in self: + record.ready_for_checkin = ( + record.allowed_checkin + and len( + record.checkin_partner_ids.filtered( + lambda c: c.state == "precheckin" + ) + ) + >= 1 + ) + + def _compute_access_url(self): + super(PmsReservation, self)._compute_access_url() + for reservation in self: + reservation.access_url = "/my/reservations/%s" % (reservation.id) + + @api.depends("reservation_line_ids") + def _compute_checkin(self): + """ + Allows to calculate the checkin by default or when the create + specifically indicates the lines of the reservation + """ + for record in self: + if record.reservation_line_ids: + checkin_line_date = min(record.reservation_line_ids.mapped("date")) + # check if the checkin was created directly as reservation_line_id: + if checkin_line_date != record.checkin: + record.checkin = checkin_line_date + elif not record.checkin: + # default checkout other folio reservations or today + if len(record.folio_id.reservation_ids) > 1: + record.checkin = record.folio_id.reservation_ids[0].checkin + else: + record.checkin = fields.date.today() + record.check_in_out_dates() + + @api.depends("reservation_line_ids", "checkin") + def _compute_checkout(self): + """ + Allows to calculate the checkout by default or when the create + specifically indicates the lines of the reservation + """ + for record in self: + if record.reservation_line_ids: + checkout_line_date = max( + record.reservation_line_ids.mapped("date") + ) + datetime.timedelta(days=1) + # check if the checkout was created directly as reservation_line_id: + if checkout_line_date != record.checkout: + record.checkout = checkout_line_date + # default checkout if checkin is set + elif record.checkin and not record.checkout: + if len(record.folio_id.reservation_ids) > 1: + record.checkin = record.folio_id.reservation_ids[0].checkout + else: + record.checkout = record.checkin + datetime.timedelta(days=1) + elif not record.checkout: + record.checkout = False + # date checking + record.check_in_out_dates() + + def _compute_precheckin_url(self): + super(PmsReservation, self)._compute_access_url() + for reservation in self: + reservation.access_url = "/my/reservations/precheckin/%s" % (reservation.id) + + @api.depends("pms_property_id", "folio_id") + def _compute_arrival_hour(self): + for record in self: + if not record.arrival_hour and record.pms_property_id: + default_arrival_hour = record.pms_property_id.default_arrival_hour + if ( + record.folio_id + and record.folio_id.reservation_ids + and record.folio_id.reservation_ids[0].arrival_hour + ): + record.arrival_hour = record.folio_id.reservation_ids[ + 0 + ].arrival_hour + else: + record.arrival_hour = default_arrival_hour + elif not record.arrival_hour: + record.arrival_hour = False + + @api.depends("pms_property_id", "folio_id") + def _compute_departure_hour(self): + for record in self: + if not record.departure_hour and record.pms_property_id: + default_departure_hour = record.pms_property_id.default_departure_hour + if ( + record.folio_id + and record.folio_id.reservation_ids + and record.folio_id.reservation_ids[0].departure_hour + ): + record.departure_hour = record.folio_id.reservation_ids[ + 0 + ].departure_hour + else: + record.departure_hour = default_departure_hour + elif not record.departure_hour: + record.departure_hour = False + + @api.depends("agency_id") + def _compute_commission_percent(self): + for reservation in self: + if reservation.agency_id: + reservation.commission_percent = ( + reservation.agency_id.default_commission + ) + else: + reservation.commission_percent = 0 + + @api.depends("commission_percent", "price_total", "service_ids") + def _compute_commission_amount(self): + for reservation in self: + if reservation.commission_percent > 0: + reservation.commission_amount = ( + reservation.price_total * reservation.commission_percent / 100 + ) + if reservation.service_ids: + for service in reservation.service_ids: + if service.is_board_service: + reservation.commission_amount = ( + reservation.commission_amount + + service.price_total + * reservation.commission_percent + / 100 + ) + else: + reservation.commission_amount = 0 + + # REVIEW: Dont run with set room_type_id -> room_id(compute)-> No set adults¿? + @api.depends("preferred_room_id", "reservation_type", "overnight_room") + def _compute_adults(self): + for reservation in self: + if not reservation.overnight_room: + reservation.adults = 0 + if reservation.preferred_room_id and reservation.reservation_type != "out": + if reservation.adults == 0: + reservation.adults = reservation.preferred_room_id.capacity + elif not reservation.adults or reservation.reservation_type == "out": + reservation.adults = 0 + + @api.depends("reservation_line_ids", "reservation_line_ids.room_id") + def _compute_splitted(self): + # REVIEW: Updating preferred_room_id here avoids cyclical dependency + for reservation in self: + room_ids = reservation.reservation_line_ids.mapped("room_id.id") + if len(room_ids) > 1 and not self._context.get("not_split"): + reservation.splitted = True + reservation.preferred_room_id = False + else: + reservation.splitted = False + # Set automatically preferred_room_id if, and only if, + # all nights has the same room + if ( + len(room_ids) == 1 + and len(reservation.reservation_line_ids) + == (reservation.checkout - reservation.checkin).days + ): + reservation.preferred_room_id = room_ids[0] + + @api.depends( + "sale_line_ids", + "sale_line_ids.invoice_status", + ) + def _compute_invoice_status(self): + """ + Compute the invoice status of a Reservation. Possible statuses: + Base on folio sale line invoice status + """ + for line in self: + states = list(set(line.sale_line_ids.mapped("invoice_status"))) + if len(states) == 1: + line.invoice_status = states[0] + elif len(states) >= 1: + if "to_invoice" in states: + line.invoice_status = "to_invoice" + elif "invoiced" in states: + line.invoice_status = "invoiced" + else: + line.invoice_status = "no" + else: + line.invoice_status = "no" + if line.reservation_type not in ("normal", "staff"): + line.invoice_status = "no" + + @api.depends("reservation_line_ids") + def _compute_nights(self): + for res in self: + res.nights = len(res.reservation_line_ids) + + @api.depends("service_ids.price_total", "services_discount") + def _compute_price_services(self): + for record in self: + record.price_services = ( + sum(record.mapped("service_ids.price_total")) - record.services_discount + ) + + @api.depends("price_services", "price_total") + def _compute_price_room_services_set(self): + for record in self: + record.price_room_services_set = record.price_services + record.price_total + + @api.depends( + "reservation_line_ids.discount", + "reservation_line_ids.cancel_discount", + ) + def _compute_discount(self): + for record in self: + discount = 0 + for line in record.reservation_line_ids: + first_discount = line.price * ((line.discount or 0.0) * 0.01) + price = line.price - first_discount + cancel_discount = price * ((line.cancel_discount or 0.0) * 0.01) + discount += first_discount + cancel_discount + + record.discount = discount + + @api.depends("service_ids.discount") + def _compute_services_discount(self): + for record in self: + services_discount = 0 + for service in record.service_ids: + services_discount += service.discount + record.services_discount = services_discount + + @api.depends("reservation_line_ids.price", "discount", "tax_ids") + def _compute_amount_reservation(self): + """ + Compute the amounts of the reservation. + """ + for record in self: + amount_room = sum(record.reservation_line_ids.mapped("price")) + if amount_room > 0: + product = record.room_type_id.product_id + price = amount_room - record.discount + taxes = record.tax_ids.compute_all( + price, record.currency_id, 1, product=product + ) + record.update( + { + "price_tax": sum( + t.get("amount", 0.0) for t in taxes.get("taxes", []) + ), + "price_total": taxes["total_included"], + "price_subtotal": taxes["total_excluded"], + } + ) + else: + record.update( + { + "price_tax": 0, + "price_total": 0, + "price_subtotal": 0, + } + ) + + def _compute_shared_folio(self): + # Has this reservation more charges associates in folio?, + # Yes?, then, this is share folio ;) + for record in self: + if record.folio_id: + record.shared_folio = len(record.folio_id.reservation_ids) > 1 or any( + record.folio_id.service_ids.filtered( + lambda x: x.reservation_id.id != record.id + ) + ) + else: + record.shared_folio = False + + @api.depends( + "partner_id", + "partner_id.name", + "agency_id", + "reservation_type", + "out_service_description", + ) + def _compute_partner_name(self): + for record in self: + if record.partner_id and record.partner_id != record.agency_id: + record.partner_name = record.partner_id.name + if record.folio_id and not record.partner_name: + record.partner_name = record.folio_id.partner_name + elif record.agency_id and not record.partner_name: + # if the customer not is the agency but we dont know the customer's name, + # set the name provisional + record.partner_name = _("Reservation from ") + record.agency_id.name + elif not record.partner_name: + record.partner_name = False + + @api.depends("partner_id", "partner_id.email", "agency_id") + def _compute_email(self): + for record in self: + self.env["pms.folio"]._apply_email(record) + + @api.depends("partner_id", "partner_id.mobile", "agency_id") + def _compute_mobile(self): + for record in self: + self.env["pms.folio"]._apply_mobile(record) + + @api.depends( + "partner_name", + "email", + "mobile", + "partner_id", + ) + def _compute_partner_incongruences(self): + fields_mapping = { + "partner_name": "name", + "email": "email", + "mobile": "mobile", + } + for record in self: + incongruous_fields = False + if record.partner_id: + for k, v in fields_mapping.items(): + if record.partner_id[v] and record.partner_id[v] != record[k]: + if not incongruous_fields: + incongruous_fields = v + else: + incongruous_fields += ", " + v + if incongruous_fields: + record.partner_incongruences = ( + incongruous_fields + " field/s don't correspond to saved host" + ) + else: + record.partner_incongruences = False + else: + record.partner_incongruences = False + + def _compute_checkin_partner_count(self): + for record in self: + if record.reservation_type != "out" and record.overnight_room: + record.checkin_partner_count = len(record.checkin_partner_ids) + record.checkin_partner_pending_count = record.adults - len( + record.checkin_partner_ids + ) + else: + record.checkin_partner_count = 0 + record.checkin_partner_pending_count = 0 + + @api.depends("room_type_id") + def _compute_tax_ids(self): + for record in self: + record = record.with_company(record.company_id) + product = self.env["product.product"].browse( + record.room_type_id.product_id.id + ) + record.tax_ids = product.taxes_id.filtered( + lambda t: t.company_id == record.env.company + ) + + @api.depends("reservation_line_ids", "reservation_line_ids.room_id") + def _compute_rooms(self): + self.rooms = False + for reservation in self: + if reservation.splitted: + reservation.rooms = ", ".join( + [r for r in reservation.reservation_line_ids.mapped("room_id.name")] + ) + else: + reservation.rooms = reservation.preferred_room_id.name + + @api.depends("folio_id", "folio_id.reservation_type") + def _compute_reservation_type(self): + for record in self: + if record.folio_id: + record.reservation_type = record.folio_id.reservation_type + else: + record.reservation_type = "normal" + + @api.depends("email", "mobile", "partner_name") + def _compute_possible_existing_customer_ids(self): + for record in self: + if record.partner_name: + possible_customer = self.env[ + "pms.folio" + ]._apply_possible_existing_customer_ids( + record.email, record.mobile, record.partner_id + ) + if possible_customer: + record.possible_existing_customer_ids = possible_customer + else: + record.possible_existing_customer_ids = False + else: + record.possible_existing_customer_ids = False + + @api.depends("reservation_type") + def _compute_avoid_mails(self): + for record in self: + if record.reservation_type == "out": + record.avoid_mails = True + elif not record.avoid_mails: + record.avoid_mails = False + + @api.depends("reservation_type", "state") + def _compute_to_send_confirmation_mail(self): + for record in self: + if record.state in ("confirm") and not record.avoid_mails: + record.to_send_confirmation_mail = True + else: + record.to_send_confirmation_mail = False + + @api.depends("checkin", "checkout") + def _compute_to_send_modification_mail(self): + for record in self: + if ( + record.state == "confirm" + and not record.to_send_confirmation_mail + and not record.avoid_mails + and ( + record._origin.checkin != record.checkin + or record._origin.checkout != record.checkout + ) + ): + record.to_send_modification_mail = True + else: + record.to_send_modification_mail = False + + @api.depends("reservation_type", "state") + def _compute_to_send_exit_mail(self): + for record in self: + if record.state in ("done") and not record.avoid_mails: + record.to_send_exit_mail = True + else: + record.to_send_exit_mail = False + + @api.depends("reservation_type", "state") + def _compute_to_send_cancelation_mail(self): + for record in self: + if record.state in ("cancel") and not record.avoid_mails: + record.to_send_cancelation_mail = True + else: + record.to_send_cancelation_mail = False + + @api.depends("partner_id") + def _compute_lang(self): + for record in self: + if record.partner_id: + record.lang = record.partner_id.lang + else: + record.lang = self.env["res.lang"].get_installed() + + @api.depends( + "reservation_line_ids", + "reservation_line_ids.sale_channel_id", + "service_ids", + "service_ids.sale_channel_origin_id", + ) + def _compute_sale_channel_ids(self): + for record in self: + sale_channel_ids = [] + if record.reservation_line_ids: + for sale in record.reservation_line_ids.mapped("sale_channel_id.id"): + sale_channel_ids.append(sale) + if record.service_ids: + for sale in record.service_ids.mapped("sale_channel_origin_id.id"): + sale_channel_ids.append(sale) + sale_channel_ids = list(set(sale_channel_ids)) + record.sale_channel_ids = [(6, 0, sale_channel_ids)] + + @api.depends("agency_id") + def _compute_sale_channel_origin_id(self): + for record in self: + # if record.folio_id.sale_channel_origin_id and not record.sale_channel_origin_id: + # record.sale_channel_origin_id = record.folio_id.sale_channel_origin_id + if record.agency_id: + record.sale_channel_origin_id = record.agency_id.sale_channel_id + + @api.depends("sale_channel_origin_id") + def _compute_is_origin_channel_check_visible(self): + for record in self: + if ( + record.sale_channel_origin_id != record.folio_id.sale_channel_origin_id + and record.folio_id + # and isinstance(self.id, int) + and record._origin.sale_channel_origin_id.id + ): + record.is_origin_channel_check_visible = True + else: + record.is_origin_channel_check_visible = False + + @api.depends("sale_channel_origin_id") + def _compute_force_update_origin(self): + for record in self: + record.force_update_origin = True + + @api.depends("is_reselling") + def _compute_is_reselling(self): + for record in self: + if any(record.reservation_line_ids.mapped("is_reselling")): + record.is_reselling = True + else: + record.is_reselling = False + + @api.depends("reservation_line_ids") + def _compute_count_alternative_free_rooms(self): + for record in self: + record.count_alternative_free_rooms = record.pms_property_id.with_context( + checkin=record.checkin, + checkout=record.checkout, + real_avail=True, + capacity=record.adults, + class_id=record.room_type_id.class_id.id, + current_lines=record.reservation_line_ids.ids, + ).availability + + def _search_allowed_checkin(self, operator, value): + if operator not in ("=",): + raise UserError( + _("Invalid domain operator %s for left of checkin", operator) + ) + + if value not in (True,): + raise UserError( + _("Invalid domain right operand %s for left of checkin", value) + ) + + today = fields.Date.context_today(self) + return [ + ("state", "in", ("draft", "confirm", "arrival_delayed")), + ("checkin", "<=", today), + ("adults", ">", 0), + ] + + def _search_allowed_checkout(self, operator, value): + if operator not in ("=",): + raise UserError( + _("Invalid domain operator %s for left of checkout", operator) + ) + + if value not in (True,): + raise UserError( + _("Invalid domain right operand %s for left of checkout", value) + ) + + today = fields.Date.context_today(self) + return [ + ("state", "in", ("onboard", "departure_delayed")), + ("checkout", ">=", today), + ("adults", ">", 0), + ] + + def _search_allowed_cancel(self, operator, value): + if operator not in ("=",): + raise UserError( + _("Invalid domain operator %s for left of cancel", operator) + ) + + if value not in (True,): + raise UserError( + _("Invalid domain right operand %s for left of cancel", value) + ) + return [ + ("state", "not in", ("cancel", "done", "departure_delayed")), + ] + + def _search_checkin_partner_pending(self, operator, value): + self.ensure_one() + recs = self.search([]).filtered(lambda x: x.checkin_partner_pending_count > 0) + return [("id", "in", [x.id for x in recs])] if recs else [] + + @api.depends("reservation_line_ids", "reservation_line_ids.overbooking") + def _compute_overbooking(self): + for record in self: + record.overbooking = any(record.reservation_line_ids.mapped("overbooking")) + + def _get_default_segmentation(self): + folio = False + segmentation_ids = False + if "folio_id" in self._context: + folio = self.env["pms.folio"].search( + [("id", "=", self._context["folio_id"])] + ) + if folio and folio.segmentation_ids: + segmentation_ids = folio.segmentation_ids + return segmentation_ids + + def _get_default_sale_channel_origin(self): + folio = False + sale_channel_origin_id = False + if "default_folio_id" in self._context: + folio = self.env["pms.folio"].search( + [("id", "=", self._context["default_folio_id"])] + ) + if folio and folio.sale_channel_origin_id: + sale_channel_origin_id = folio.sale_channel_origin_id + return sale_channel_origin_id + + def check_in_out_dates(self): + """ + 1.-When date_order is less then checkin date or + Checkout date should be greater than the checkin date. + 3.-Check the reservation dates are not occuped + """ + for record in self: + if ( + record.checkout + and record.checkout + and record.checkin >= record.checkout + ): + raise UserError( + _( + "Room line Check In Date Should be \ + less than the Check Out Date!" + ) + ) + + def _check_capacity(self): + for record in self: + if record.reservation_type != "out": + self.env["pms.room"]._check_adults( + record, record.service_ids.service_line_ids + ) + + @api.constrains("reservation_line_ids") + def checkin_checkout_consecutive_dates(self): + """ + simply convert date objects to integers using the .toordinal() method + of datetime objects. The difference between the maximum and minimum value + of the set of ordinal dates is one more than the length of the set + """ + for record in self: + if min(record.reservation_line_ids.mapped("date")) != record.checkin: + raise UserError( + _( + """ + Compute error: The first room line date should + be the same as the checkin date! + """ + ) + ) + if max( + record.reservation_line_ids.mapped("date") + ) != record.checkout - datetime.timedelta(days=1): + raise UserError( + _( + """ + Compute error: The last room line date should + be the previous day of the checkout date! + """ + ) + ) + if record.reservation_line_ids and len(record.reservation_line_ids) > 1: + dates = record.reservation_line_ids.mapped("date") + date_ints = {d.toordinal() for d in dates} + if not (max(date_ints) - min(date_ints) == len(date_ints) - 1): + raise ValidationError(_("Reservation dates should be consecutives")) + + # @api.constrains("checkin_partner_ids", "adults") + # def _max_checkin_partner_ids(self): + # for record in self: + # if len(record.checkin_partner_ids) > record.adults: + # raise models.ValidationError( + # _("The room already is completed (%s)", record.name) + # ) + + @api.constrains("state") + def _check_onboard_reservation(self): + for record in self: + if ( + not record.checkin_partner_ids.filtered(lambda c: c.state == "onboard") + and record.state == "onboard" + and record.reservation_type != "out" + ): + raise ValidationError( + _("No person from reserve %s has arrived", record.name) + ) + if record.state == "cancel" and not self.env.context.get("action_cancel"): + raise ValidationError( + _("The reservation must be canceled by action: action_cancel") + ) + if record.state == "confirm" and not self.env.context.get("action_confirm"): + raise ValidationError( + _("The reservation must be confirmed by action: action_confirm") + ) + + @api.constrains("arrival_hour") + def _check_arrival_hour(self): + for record in self: + if record.arrival_hour: + try: + time.strptime(record.arrival_hour, "%H:%M") + return True + except ValueError: + raise ValidationError( + _("Format Arrival Hour (HH:MM) Error: %s", record.arrival_hour) + ) + + @api.constrains("departure_hour") + def _check_departure_hour(self): + for record in self: + if record.departure_hour: + try: + time.strptime(record.departure_hour, "%H:%M") + return True + except ValueError: + raise ValidationError( + _( + "Format Departure Hour (HH:MM) Error: %s", + record.departure_hour, + ) + ) + + @api.constrains("agency_id") + def _no_agency_as_agency(self): + for record in self: + if record.agency_id and not record.agency_id.is_agency: + raise ValidationError(_("booking agency with wrong configuration: ")) + + @api.constrains("closure_reason_id") + def _check_closure_reason_id(self): + for record in self: + if record.reservation_type == "out": + if not record.closure_reason_id: + raise ValidationError( + _( + "A closure reason is mandatory when reservation" + " type is 'out of service'" + ) + ) + + @api.constrains("reservation_type") + def _check_same_reservation_type(self): + for record in self: + if len(record.folio_id.reservation_ids) > 1: + for reservation in record.folio_id.reservation_ids: + if reservation.reservation_type != record.reservation_type: + raise ValidationError( + _( + "The reservation type must be the " + "same for all reservations in folio" + ) + ) + + # @api.constrains("sale_channel_ids") + # def _check_lines_with_sale_channel_id(self): + # for record in self.filtered("sale_channel_origin_id"): + # if record.reservation_line_ids: + # if record.sale_channel_origin_id not in record.sale_channel_ids: + # raise ValidationError( + # _( + # "Reservation must have one reservation line " + # "with sale channel equal to sale channel origin of reservation." + # "Change sale_channel_origin of reservation before" + # ) + # ) + + # Action methods + def open_partner(self): + """Utility method used to add an "View Customer" button in reservation views""" + self.ensure_one() + partner_form_id = self.env.ref("pms.view_partner_data_form").id + return { + "type": "ir.actions.act_window", + "res_model": "res.partner", + "view_mode": "form", + "views": [(partner_form_id, "form")], + "res_id": self.partner_id.id, + "target": "new", + "flags": {"form": {"action_buttons": True}}, + } + + def print_all_checkins(self): + checkins = self.env["pms.checkin.partner"] + for record in self: + checkins += record.checkin_partner_ids.filtered( + lambda s: s.state in ("precheckin", "onboard", "done") + ) + if checkins: + return self.env.ref("pms.action_traveller_report").report_action(checkins) + else: + raise ValidationError(_("There are no checkins to print")) + + def open_folio(self): + action = self.env.ref("pms.open_pms_folio1_form_tree_all").sudo().read()[0] + if self.folio_id: + action["views"] = [(self.env.ref("pms.pms_folio_view_form").id, "form")] + action["res_id"] = self.folio_id.id + else: + action = {"type": "ir.actions.act_window_close"} + return action + + def open_reservation_form(self): + action = self.env.ref("pms.open_pms_reservation_form_tree_all").sudo().read()[0] + action["views"] = [(self.env.ref("pms.pms_reservation_view_form").id, "form")] + action["res_id"] = self.id + return action + + def action_pay_folio(self): + self.ensure_one() + return self.folio_id.action_pay() + + def open_reservation_wizard(self): + pms_property = self.pms_property_id + pms_property = pms_property.with_context( + checkin=self.checkin, + checkout=self.checkout, + current_lines=self.reservation_line_ids.ids, + pricelist_id=self.pricelist_id.id, + ) + rooms_available = pms_property.free_room_ids + + # REVIEW: check capacity room + return { + "view_type": "form", + "view_mode": "form", + "name": "Unify the reservation", + "res_model": "pms.reservation.split.join.swap.wizard", + "target": "new", + "type": "ir.actions.act_window", + "context": { + "rooms_available": rooms_available.ids, + }, + } + + def action_open_confirmation_mail_composer(self): + return self.folio_id.action_open_confirmation_mail_composer() + + def action_open_modification_mail_composer(self): + return self.folio_id.action_open_modification_mail_composer() + + def action_open_exit_mail_composer(self): + return self.folio_id.action_open_exit_mail_composer() + + def action_open_cancelation_mail_composer(self): + return self.folio_id.action_open_cancelation_mail_composer() + + def open_wizard_several_partners(self): + ctx = dict( + reservation_id=self.id, + possible_existing_customer_ids=self.possible_existing_customer_ids.ids, + ) + return { + "view_type": "form", + "view_mode": "form", + "name": "Several Customers", + "res_model": "pms.several.partners.wizard", + "target": "new", + "type": "ir.actions.act_window", + "context": ctx, + } + + @api.model + def name_search(self, name="", args=None, operator="ilike", limit=100): + if args is None: + args = [] + if not (name == "" and operator == "ilike"): + args += [ + "|", + ("name", operator, name), + ("folio_id.name", operator, name), + ("preferred_room_id.name", operator, name), + ] + return super(PmsReservation, self).name_search( + name="", args=args, operator="ilike", limit=limit + ) + + def name_get(self): + result = [] + for res in self: + name = "{} ({})".format(res.name, res.rooms if res.rooms else "No room") + result.append((res.id, name)) + return result + + @api.model + def create(self, vals): + if vals.get("folio_id"): + folio = self.env["pms.folio"].browse(vals["folio_id"]) + default_vals = {"pms_property_id": folio.pms_property_id.id} + if folio.partner_id: + default_vals["partner_id"] = folio.partner_id.id + elif folio.partner_name: + default_vals["partner_name"] = folio.partner_name + default_vals["mobile"] = folio.mobile + default_vals["email"] = folio.email + elif vals.get("reservation_type") != "out": + raise ValidationError(_("Partner contact name is required")) + if folio.sale_channel_origin_id and "sale_channel_origin_id" not in vals: + default_vals["sale_channel_origin_id"] = folio.sale_channel_origin_id.id + vals.update(default_vals) + elif ( + "pms_property_id" in vals + and "sale_channel_origin_id" in vals + and ("partner_name" in vals or "partner_id" in vals or "agency_id" in vals) + ): + folio_vals = self._get_folio_vals(vals) + + self._check_clousure_reason( + reservation_type=vals.get("reservation_type"), + closure_reason_id=vals.get("closure_reason_id"), + ) + + # Create the folio in case of need + # (To allow to create reservations direct) + folio = self.env["pms.folio"].create(folio_vals) + vals.update( + { + "folio_id": folio.id, + "reservation_type": vals.get("reservation_type"), + } + ) + + else: + raise ValidationError( + _( + "The Property and Sale Channel Origin are mandatory in the reservation" + ) + ) + if vals.get("name", _("New")) == _("New") or "name" not in vals: + folio_sequence = ( + max(folio.mapped("reservation_ids.folio_sequence")) + 1 + if folio.reservation_ids + else 1 + ) + vals["folio_sequence"] = folio_sequence + vals["name"] = folio.name + "/" + str(folio_sequence) + if not vals.get("reservation_type"): + vals["reservation_type"] = ( + folio.reservation_type if folio.reservation_type else "normal" + ) + # Avoid send state field in vals, with the propouse to + # use action_confirm or action_cancel methods + reservation_state = False + if "state" in vals: + reservation_state = vals["state"] + vals.pop("state") + record = super(PmsReservation, self).create(vals) + record._check_capacity() + if ( + record.preconfirm and record.state == "draft" + ) or reservation_state == "confirm": + record.action_confirm() + elif reservation_state == "cancel": + record.action_cancel() + + record._check_services(vals) + return record + + def write(self, vals): + if ( + any([record.blocked for record in self]) + and not self.env.context.get("force_write_blocked") + and ( + "checkin" in vals + or "checkout" in vals + or "room_type_id" in vals + or ( + vals.get("reservation_line_ids") + and any( + [ + ( + "date" in line[2] + and ( + datetime.datetime.strptime( + line[2]["date"], "%Y-%m-%d" + ).date() + != self.env["pms.reservation.line"] + .browse(line[1]) + .date + ) + ) + or ( + "price" in line[2] + and ( + round(line[2]["price"], 2) + != round( + self.env["pms.reservation.line"] + .browse(line[1]) + .price, + 2, + ) + ) + ) + for line in vals.get("reservation_line_ids") + if line[0] == 1 + ] + ) + ) + ) + ): + raise ValidationError(_("Blocked reservations can't be modified")) + folios_to_update_channel = self.env["pms.folio"] + lines_to_update_channel = self.env["pms.reservation.line"] + services_to_update_channel = self.env["pms.service"] + if "sale_channel_origin_id" in vals: + folios_to_update_channel = self.get_folios_to_update_channel(vals) + lines_to_update_channel = self.get_lines_to_update_channel(vals) + services_to_update_channel = self.get_services_to_update_channel(vals) + res = super(PmsReservation, self).write(vals) + if folios_to_update_channel: + folios_to_update_channel.sale_channel_origin_id = vals[ + "sale_channel_origin_id" + ] + if lines_to_update_channel: + lines_to_update_channel.sale_channel_id = vals["sale_channel_origin_id"] + if services_to_update_channel: + services_to_update_channel.sale_channel_origin_id = vals[ + "sale_channel_origin_id" + ] + + self._check_services(vals) + # Only check if adult to avoid to check capacity in intermediate states (p.e. flush) + # that not take access to possible extra beds service in vals + if "adults" in vals: + self._check_capacity() + return res + + def _get_folio_vals(self, reservation_vals): + folio_vals = { + "pms_property_id": reservation_vals["pms_property_id"], + } + if reservation_vals.get("sale_channel_origin_id"): + folio_vals["sale_channel_origin_id"] = reservation_vals.get( + "sale_channel_origin_id" + ) + if reservation_vals.get("partner_id"): + folio_vals["partner_id"] = reservation_vals.get("partner_id") + elif reservation_vals.get("agency_id"): + folio_vals["agency_id"] = reservation_vals.get("agency_id") + elif reservation_vals.get("partner_name"): + folio_vals["partner_name"] = reservation_vals.get("partner_name") + folio_vals["mobile"] = reservation_vals.get("mobile") + folio_vals["email"] = reservation_vals.get("email") + elif reservation_vals.get("reservation_type") != "out": + raise ValidationError(_("Partner contact name is required")) + if reservation_vals.get("reservation_type"): + folio_vals["reservation_type"] = reservation_vals.get("reservation_type") + return folio_vals + + def _check_clousure_reason(self, reservation_type, closure_reason_id): + if reservation_type == "out" and not closure_reason_id: + raise ValidationError( + _( + "A closure reason is mandatory when reservation" + " type is 'out of service'" + ) + ) + + def _check_services(self, vals): + # If we create a reservation with board service and other service at the same time, + # compute_service_ids dont run (compute with readonly to False), + # and we must force it to compute the services linked with the board service: + if "board_service_room_id" in vals and "service_ids" in vals: + self._compute_service_ids() + + def get_folios_to_update_channel(self, vals): + folios_to_update_channel = self.env["pms.folio"] + for folio in self.mapped("folio_id"): + if ( + any( + res.sale_channel_origin_id == folio.sale_channel_origin_id + for res in self.filtered(lambda r: r.folio_id == folio) + ) + and vals["sale_channel_origin_id"] != folio.sale_channel_origin_id.id + and ( + ("force_update_origin" in vals and vals.get("force_update_origin")) + or len(folio.reservation_ids) == 1 + ) + ): + folios_to_update_channel += folio + return folios_to_update_channel + + def get_lines_to_update_channel(self, vals): + lines_to_update_channel = self.env["pms.reservation.line"] + for record in self: + for line in record.reservation_line_ids: + if line.sale_channel_id == record.sale_channel_origin_id and ( + vals["sale_channel_origin_id"] != line.sale_channel_id.id + ): + lines_to_update_channel += line + return lines_to_update_channel + + def get_services_to_update_channel(self, vals): + services_to_update_channel = self.env["pms.service"] + for record in self: + for service in record.service_ids: + if ( + service.sale_channel_origin_id == record.sale_channel_origin_id + and ( + vals["sale_channel_origin_id"] + != service.sale_channel_origin_id.id + ) + ): + services_to_update_channel += service + return services_to_update_channel + + def update_prices(self): + self.ensure_one() + for line in self.reservation_line_ids: + line.with_context(force_recompute=True)._compute_price() + self.show_update_pricelist = False + self.message_post( + body=_( + """Prices have been recomputed according to pricelist %s + and room type %s""", + self.pricelist_id.display_name, + self.room_type_id.name, + ) + ) + + @api.model + def autocheckout(self, reservation): + reservation.action_reservation_checkout() + if not any( + [checkin.state == "done" for checkin in reservation.checkin_partner_ids] + ): + msg = _("No checkin was made for this reservation") + reservation.message_post( + subject=_("No Checkins!"), subtype="mt_comment", body=msg + ) + return True + + @api.model + def update_daily_priority_reservation(self): + reservations = self.env["pms.reservation"].search([("priority", "<", 1000)]) + reservations._compute_priority() + return True + + def action_confirm(self): + for record in self: + vals = {} + if record.checkin_partner_ids.filtered(lambda c: c.state == "onboard"): + vals.update({"state": "onboard"}) + else: + vals.update({"state": "confirm"}) + record.with_context(action_confirm=True).write(vals) + record.reservation_line_ids.update({"cancel_discount": 0}) + # Unlink penalty service if exist + record.service_ids.filtered(lambda s: s.is_cancel_penalty).unlink() + if record.folio_id.state != "confirm": + record.folio_id.action_confirm() + return True + + def action_cancel(self): + for record in self: + # else state = cancel + if not record.allowed_cancel: + raise UserError(_("This reservation cannot be cancelled")) + else: + record.with_context(action_cancel=True).state = "cancel" + record._check_cancel_penalty() + record.cancel_datetime = fields.Datetime.now() + # If all reservations in the folio are cancelled, cancel the folio + if not ( + record.folio_id.reservation_ids.filtered( + lambda r: r.state != "cancel" + ) + ): + record.folio_id.action_cancel() + record.folio_id._compute_amount() + + def action_assign(self): + for record in self: + record.to_assign = False + + def _check_cancel_penalty(self): + for record in self: + # self.ensure_one() + if record.state == "cancel": + pricelist = record.pricelist_id + if record._context.get("modified", False): + record.cancelled_reason = "modified" + _logger.info("Modified Reservation - No Penalty") + continue + elif pricelist and pricelist.cancelation_rule_id: + rule = pricelist.cancelation_rule_id + tz_property = record.pms_property_id.tz + today = fields.Date.context_today( + record.with_context(tz=tz_property) + ) + days_diff = ( + fields.Date.from_string(record.checkin) + - fields.Date.from_string(today) + ).days + days = record.nights + if days_diff < 0: + record.cancelled_reason = "noshow" + penalty_percent = rule.penalty_noshow + if rule.apply_on_noshow == "first": + days = 1 + elif rule.apply_on_noshow == "days": + days = rule.days_late - 1 + elif days_diff < pricelist.cancelation_rule_id.days_intime: + record.cancelled_reason = "late" + penalty_percent = rule.penalty_late + if rule.apply_on_late == "first": + days = 1 + elif rule.apply_on_late == "days": + days = rule.days_late + else: + record.cancelled_reason = "intime" + penalty_percent = 0 + days = 0 + # Generate a penalty service in the reservation + if penalty_percent: + dates = [] + for i in range(0, days): + dates.append( + fields.Date.from_string( + fields.Date.from_string(record.checkin) + + datetime.timedelta(days=i) + ) + ) + amount_penalty = ( + sum( + record.reservation_line_ids.filtered( + lambda l: fields.Date.from_string(l.date) in dates + ).mapped("price") + ) + * penalty_percent + / 100 + ) + if not amount_penalty: + return + if not record.company_id.cancel_penalty_product_id: + # Create a penalty product + record.company_id.cancel_penalty_product_id = self.env[ + "product.product" + ].create( + { + "name": _("Cancel Penalty"), + "type": "service", + } + ) + penalty_product = record.company_id.cancel_penalty_product_id + default_channel_id = ( + self.env["pms.sale.channel"] + .search([("channel_type", "=", "direct")], limit=1) + .id, + ) + self.env["pms.service"].create( + { + "product_id": penalty_product.id, + "folio_id": record.folio_id.id, + "reservation_id": record.id, + "name": penalty_product.name, + "sale_channel_origin_id": default_channel_id, + "service_line_ids": [ + ( + 0, + 0, + { + "product_id": penalty_product.id, + "day_qty": 1, + "price_unit": amount_penalty, + "date": fields.Date.today(), + }, + ) + ], + } + ) + else: + record.cancelled_reason = False + + def action_reservation_checkout(self): + for record in self: + if not record.allowed_checkout: + raise UserError(_("This reservation cannot be check out")) + record.state = "done" + if record.checkin_partner_ids: + record.checkin_partner_ids.filtered( + lambda check: check.state == "onboard" + ).action_done() + return True + + def action_undo_onboard(self): + for record in self: + # Undo onboard all checkin partners: + record.checkin_partner_ids.filtered( + lambda check: check.state == "onboard" + ).action_undo_onboard() + record.action_confirm() + return True + + def action_checkin_partner_view(self): + self.ensure_one() + tree_id = self.env.ref("pms.pms_checkin_partner_reservation_view_tree").id + return { + "name": _("Register Partners"), + "views": [[tree_id, "tree"]], + "res_model": "pms.checkin.partner", + "type": "ir.actions.act_window", + "context": { + "create": False, + "edit": True, + "popup": True, + }, + "domain": [("reservation_id", "=", self.id), ("state", "=", "draft")], + "search_view_id": [ + self.env.ref("pms.pms_checkin_partner_view_folio_search").id, + "search", + ], + "target": "new", + } + + def action_checkin_partner_onboard_view(self): + self.ensure_one() + kanban_id = self.env.ref("pms.pms_checkin_partner_kanban_view").id + return { + "name": _("Register Checkins"), + "views": [[kanban_id, "kanban"]], + "res_model": "pms.checkin.partner", + "type": "ir.actions.act_window", + "context": { + "create": False, + "edit": True, + "popup": True, + }, + "search_view_id": [ + self.env.ref("pms.pms_checkin_partner_view_folio_search").id, + "search", + ], + "domain": [("reservation_id", "=", self.id)], + "target": "new", + } + + @api.model + def auto_arrival_delayed(self): + # No show when pass 1 day from checkin day + reservations = self.env["pms.reservation"].search( + [ + ("state", "in", ("draft", "confirm")), + ("checkin", "<", fields.Date.today()), + ("overnight_room", "=", True), + ] + ) + for reservation in reservations: + reservation.state = "arrival_delayed" + reservation.message_post( + body=_( + """No entry has been recorded in this reservation""", + ) + ) + + @api.model + def auto_departure_delayed(self): + # No checkout when pass checkout hour + reservations = self.env["pms.reservation"].search( + [ + ("state", "in", ("onboard", "departure_delayed")), + ("checkout", "<=", fields.Datetime.today().date()), + ] + ) + for reservation in reservations: + if reservation.overnight_room: + if reservation.checkout == fields.Datetime.today().date(): + reservation.state = "departure_delayed" + else: + reservation.autocheckout(reservation) + else: + reservation.state = "done" + + def preview_reservation(self): + self.ensure_one() + return { + "type": "ir.actions.act_url", + "target": "self", + "url": self.get_portal_url(), + } diff --git a/pms/models/pms_reservation_line.py b/pms/models/pms_reservation_line.py new file mode 100644 index 0000000000..c484cbbe9f --- /dev/null +++ b/pms/models/pms_reservation_line.py @@ -0,0 +1,572 @@ +# Copyright 2017-2018 Alexandre Díaz +# Copyright 2017 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import datetime +import logging + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + +_logger = logging.getLogger(__name__) + + +class PmsReservationLine(models.Model): + _name = "pms.reservation.line" + _description = "Reservations by day" + _order = "date" + _check_company_auto = True + + reservation_id = fields.Many2one( + string="Reservation", + help="It is the reservation in a reservation line", + required=True, + copy=False, + comodel_name="pms.reservation", + ondelete="cascade", + index=True, + check_pms_properties=True, + ) + room_id = fields.Many2one( + string="Room", + help="The room of a reservation. ", + readonly=False, + index=True, + store=True, + compute="_compute_room_id", + comodel_name="pms.room", + ondelete="restrict", + check_pms_properties=True, + ) + + sale_line_ids = fields.Many2many( + string="Sales Lines", + readonly=True, + copy=False, + comodel_name="folio.sale.line", + check_pms_properties=True, + ) + pms_property_id = fields.Many2one( + string="Property", + help="Property with access to the element", + readonly=True, + store=True, + comodel_name="pms.property", + related="reservation_id.pms_property_id", + index=True, + check_pms_properties=True, + ) + date = fields.Date( + string="Date", + help="The date of the reservation in reservation line", + ) + state = fields.Selection( + string="State", + help="State of the reservation line.", + related="reservation_id.state", + store=True, + ) + price = fields.Float( + string="Price", + help="The price in a reservation line", + store=True, + readonly=False, + digits=("Product Price"), + compute="_compute_price", + ) + cancel_discount = fields.Float( + string="Cancelation Discount (%)", + help="", + readonly=True, + default=0.0, + store=True, + digits=("Discount"), + compute="_compute_cancel_discount", + ) + avail_id = fields.Many2one( + string="Availability Day", + help="", + store=True, + comodel_name="pms.availability", + ondelete="restrict", + compute="_compute_avail_id", + index=True, + check_pms_properties=True, + ) + discount = fields.Float( + string="Discount (%)", + help="", + default=0.0, + digits=("Discount"), + ) + price_day_total = fields.Float( + string="Final price", + help="Get the price with discount applied", + store=True, + compute="_compute_price_day_total", + ) + occupies_availability = fields.Boolean( + string="Occupies", + help="This record is taken into account to calculate availability", + store=True, + compute="_compute_occupies_availability", + ) + overnight_room = fields.Boolean( + related="reservation_id.overnight_room", + store=True, + ) + overbooking = fields.Boolean( + string="Overbooking", + help="Indicate if exists overbooking in the reservation line", + store=True, + readonly=False, + compute="_compute_overbooking", + ) + sale_channel_id = fields.Many2one( + string="Sale Channel", + help="Sale Channel through which reservation line was created", + comodel_name="pms.sale.channel", + index=True, + check_pms_properties=True, + ) + default_invoice_to = fields.Many2one( + string="Invoice to", + help="""Indicates the contact to which this line will be + billed by default, if it is not established, + a guest or the generic contact will be used instead""", + readonly=False, + store=True, + compute="_compute_default_invoice_to", + comodel_name="res.partner", + index=True, + ondelete="restrict", + ) + + is_reselling = fields.Boolean( + string="Reselling", + help="Indicates if the reservation line is reselling", + readonly=False, + store=True, + ) + + def name_get(self): + result = [] + for res in self: + date = fields.Date.from_string(res.date) + name = "{}/{}".format(date.day, date.month) + result.append((res.id, name)) + return result + + def _get_display_price(self, product): + if self.reservation_id.pricelist_id.discount_policy == "with_discount": + return product.with_context( + pricelist=self.reservation_id.pricelist_id.id + ).standard_price + product_context = dict( + self.env.context, + partner_id=self.reservation_id.partner_id.id, + date=self.date, + uom=product.uom_id.id, + ) + final_price, rule_id = self.reservation_id.pricelist_id.with_context( + product_context + ).get_product_price_rule(product, 1.0, self.reservation_id.partner_id) + base_price, currency = self.with_context( + product_context + )._get_real_price_currency( + product, rule_id, 1, product.uom_id, self.reservation_id.pricelist_id.id + ) + if currency != self.reservation_id.pricelist_id.currency_id: + base_price = currency._convert( + base_price, + self.reservation_id.pricelist_id.currency_id, + self.reservation_id.company_id or self.env.company, + fields.Date.today(), + ) + # negative discounts (= surcharge) are included in the display price + return max(base_price, final_price) + + # flake8: noqa=C901 + @api.depends("reservation_id.room_type_id", "reservation_id.preferred_room_id") + def _compute_room_id(self): + for line in self.filtered("reservation_id.room_type_id").sorted( + key=lambda r: (r.reservation_id, r.date) + ): + reservation = line.reservation_id + if ( + reservation.preferred_room_id + and reservation.preferred_room_id != line.room_id + ) or ( + (reservation.preferred_room_id or reservation.room_type_id) + and not line.room_id + ): + free_room_select = True if reservation.preferred_room_id else False + + # we get the rooms available for the entire stay + # (real_avail if True if the reservation was created with + # specific room selected) + pms_property = line.pms_property_id + pms_property = pms_property.with_context( + checkin=reservation.checkin, + checkout=reservation.checkout, + room_type_id=reservation.room_type_id.id + if not free_room_select + else False, + current_lines=reservation.reservation_line_ids.ids, + pricelist_id=reservation.pricelist_id.id, + real_avail=True, + ) + rooms_available = pms_property.free_room_ids + + # Check if the room assigment is manual or automatic to set the + # to_assign value on reservation + manual_assigned = False + if ( + free_room_select + and reservation.preferred_room_id.id + not in reservation.reservation_line_ids.room_id.ids + and self.env.user._is_property_member(pms_property.id) + ): + # This case is a preferred_room_id manually assigned + manual_assigned = True + # if there is availability for the entire stay + if rooms_available: + # Avoid that reservation._compute_splitted set the + # reservation like splitted in intermediate calculations + reservation = reservation.with_context(not_split=True) + # if the reservation has a preferred room + if reservation.preferred_room_id: + + # if the preferred room is available + if reservation.preferred_room_id in rooms_available: + line.room_id = reservation.preferred_room_id + reservation.to_assign = ( + False if manual_assigned else reservation.to_assign + ) + + # if the preferred room is NOT available + else: + if ( + self.env.context.get("force_overbooking") + or not line.occupies_availability + ): + line.room_id = reservation.preferred_room_id + else: + raise ValidationError( + _("%s: No room available in %s <-> %s.") + % ( + reservation.preferred_room_id.name, + reservation.checkin, + reservation.checkout, + ) + ) + + # otherwise we assign the first of those + # available for the entire stay + else: + line.room_id = rooms_available[0] + # check that the reservation cannot be allocated even by dividing it + elif not self.env["pms.property"].splitted_availability( + checkin=reservation.checkin, + checkout=reservation.checkout, + room_type_id=reservation.room_type_id.id, + current_lines=line._origin.reservation_id.reservation_line_ids.ids, + pricelist=reservation.pricelist_id, + pms_property_id=line.pms_property_id.id, + real_avail=True, + ): + if self.env.context.get("force_overbooking"): + line.room_id = reservation.room_type_id.room_ids.filtered( + lambda r: r.pms_property_id == line.pms_property_id + )[0] + else: + raise ValidationError( + _("%s: No room type available") + % (reservation.room_type_id.name) + ) + + # the reservation can be allocated into several rooms + else: + rooms_ranking = dict() + + # we go through the rooms of the type + for room in self.env["pms.room"].search( + [ + ("room_type_id", "=", reservation.room_type_id.id), + ("pms_property_id", "=", reservation.pms_property_id.id), + ] + ): + # we iterate the dates from the date of the line to the checkout + for date_iterator in [ + line.date + datetime.timedelta(days=x) + for x in range(0, (reservation.checkout - line.date).days) + ]: + # if the room is already assigned for + # a date we go to the next room + ids = reservation.reservation_line_ids.ids + if ( + self.env["pms.reservation.line"].search_count( + [ + ("date", "=", date_iterator), + ("room_id", "=", room.id), + ("id", "not in", ids), + ("occupies_availability", "=", True), + ] + ) + > 0 + ): + break + # if the room is not assigned for a date we + # add it to the ranking / update its ranking + else: + rooms_ranking[room.id] = ( + 1 + if room.id not in rooms_ranking + else rooms_ranking[room.id] + 1 + ) + + if len(rooms_ranking) > 0: + # we get the best score in the ranking + best = max(rooms_ranking.values()) + + # we keep the rooms with the best ranking + bests = { + key: value + for (key, value) in rooms_ranking.items() + if value == best + } + + # if there is a tie in the rankings + if len(bests) > 1: + + # we get the line from last night + date_last_night = line.date + datetime.timedelta(days=-1) + line_past_night = self.env["pms.reservation.line"].search( + [ + ("date", "=", date_last_night), + ("reservation_id", "=", reservation.id), + ] + ) + # if there is the night before and if the room + # from the night before is in the ranking + if line_past_night and line_past_night.room_id.id in bests: + line.room_id = line_past_night.room_id.id + + # if the room from the night before is not in the ranking + # or there is no night before + else: + # At this point we set the room with the best ranking, + # no matter what it is + line.room_id = list(bests.keys())[0] + + # if there is no tie in the rankings + else: + # At this point we set the room with the best ranking, + # no matter what it is + line.room_id = list(bests.keys())[0] + + @api.depends( + "reservation_id", + "reservation_id.room_type_id", + "reservation_id.reservation_type", + "reservation_id.pms_property_id", + ) + def _compute_price(self): + for line in self: + reservation = line.reservation_id + if ( + not reservation.room_type_id + or not reservation.pricelist_id + or not reservation.pms_property_id + or reservation.reservation_type != "normal" + ): + line.price = 0 + elif not line.price or self._context.get("force_recompute"): + room_type_id = reservation.room_type_id.id + product = self.env["pms.room.type"].browse(room_type_id).product_id + partner = self.env["res.partner"].browse(reservation.partner_id.id) + product = product.with_context( + lang=partner.lang, + partner=partner.id, + quantity=1, + date=reservation.date_order, + consumption_date=line.date, + pricelist=reservation.pricelist_id.id, + uom=product.uom_id.id, + property=reservation.pms_property_id.id, + ) + line.price = self.env["account.tax"]._fix_tax_included_price_company( + line._get_display_price(product), + product.taxes_id, + reservation.tax_ids, + reservation.pms_property_id.company_id, + ) + # TODO: Out of service 0 amount + + @api.depends("reservation_id.state", "is_reselling") + def _compute_occupies_availability(self): + for line in self: + if line.reservation_id.state == "cancel" or line.is_reselling: + line.occupies_availability = False + else: + line.occupies_availability = True + + # TODO: Refact method and allowed cancelled single days + @api.depends("reservation_id.cancelled_reason", "reservation_id.state") + def _compute_cancel_discount(self): + for line in self: + if line.state == "cancel": + line.cancel_discount = 100 + else: + line.cancel_discount = 0 + + @api.depends("room_id", "pms_property_id", "date", "occupies_availability") + def _compute_avail_id(self): + for record in self: + if record.room_id.room_type_id and record.date and record.pms_property_id: + avail = self.env["pms.availability"].search( + [ + ("date", "=", record.date), + ("room_type_id", "=", record.room_id.room_type_id.id), + ("pms_property_id", "=", record.pms_property_id.id), + ] + ) + if avail: + room_ids = record.room_id.room_type_id.room_ids.filtered( + lambda r: r.pms_property_id == record.pms_property_id + ).ids + if ( + record.occupies_availability + and not ( + self.env.context.get("avoid_availability_check", False) + or self.env.context.get("force_overbooking", False) + ) + and record.room_id.id + in avail.get_rooms_not_avail( + checkin=record.date, + checkout=record.date + datetime.timedelta(1), + room_ids=room_ids, + pms_property_id=record.pms_property_id.id, + current_lines=record.ids, + ) + ): + raise ValidationError( + _("There is no availability for the room type %s on %s") + % (record.room_id.room_type_id.name, record.date) + ) + record.avail_id = avail.id + else: + record.avail_id = self.env["pms.availability"].create( + { + "date": record.date, + "room_type_id": record.room_id.room_type_id.id, + "pms_property_id": record.pms_property_id.id, + } + ) + else: + record.avail_id = False + + @api.depends("price", "discount", "cancel_discount") + def _compute_price_day_total(self): + for line in self: + first_discount = line.price * ((line.discount or 0.0) * 0.01) + price = line.price - first_discount + cancel_discount = price * ((line.cancel_discount or 0.0) * 0.01) + discount = first_discount + cancel_discount + line.price_day_total = line.price - discount + + @api.depends("room_id", "state", "is_reselling") + def _compute_overbooking(self): + for record in self.filtered("room_id"): + overbooking = False + if record.state != "cancel" and not record.is_reselling: + record_id = ( + record.id + if isinstance(record, int) + else record._origin.id + if hasattr(record, "_origin") + else False + ) + if self.env["pms.reservation.line"].search( + [ + ("date", "=", record.date), + ("room_id", "=", record.room_id.id), + ("id", "!=", record_id), + ("occupies_availability", "=", True), + ("overbooking", "=", False), + ] + ): + overbooking = True + record.overbooking = overbooking + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + if vals.get("reservation_id") and not vals.get("sale_channel_id"): + reservation = self.env["pms.reservation"].browse( + vals.get("reservation_id") + ) + vals["sale_channel_id"] = reservation.sale_channel_origin_id.id + records = super().create(vals_list) + for line in records: + reservation = line.reservation_id + # Set default channel + if not line.sale_channel_id: + line.sale_channel_id = reservation.sale_channel_origin_id.id + # Update quota + self.env["pms.availability.plan"].update_quota( + pricelist_id=reservation.pricelist_id.id, + room_type_id=reservation.room_type_id.id, + date=line.date, + pms_property_id=reservation.pms_property_id.id, + ) + return records + + @api.depends("sale_channel_id", "reservation_id.agency_id") + def _compute_default_invoice_to(self): + for record in self: + agency = record.reservation_id.agency_id + if ( + agency + and agency.invoice_to_agency == "always" + and agency.sale_channel_id == record.sale_channel_id + ): + record.default_invoice_to = agency + elif not record.default_invoice_to: + record.default_invoice_to = False + + def write(self, vals): + if not self.env.context.get("force_write_blocked") and ( + ( + "price" in vals + and any( + [ + vals["date"] != record.date + for record in self + if record.reservation_id.blocked + ] + ) + ) + or ( + "price" in vals + and any( + [ + round(vals["price"], 2) != round(record.price, 2) + for record in self + if record.reservation_id.blocked + ] + ) + ) + ): + raise ValidationError(_("Blocked reservations can't be modified")) + res = super().write(vals) + return res + + # Constraints and onchanges + @api.constrains("date") + def constrains_duplicated_date(self): + for record in self: + duplicated = record.reservation_id.reservation_line_ids.filtered( + lambda r: r.date == record.date and r.id != record.id + ) + if duplicated: + raise ValidationError(_("Duplicated reservation line date")) diff --git a/pms/models/pms_room.py b/pms/models/pms_room.py new file mode 100644 index 0000000000..2823f00547 --- /dev/null +++ b/pms/models/pms_room.py @@ -0,0 +1,248 @@ +# Copyright 2017 Alexandre Díaz +# Copyright 2017 Dario Lodeiros +# Copyright 2018 Pablo Quesada +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class PmsRoom(models.Model): + """The rooms for lodging can be for sleeping, usually called rooms, + and also for speeches (conference rooms), parking, + relax with cafe con leche, spa... + """ + + _name = "pms.room" + _description = "Property Room" + _order = "sequence, room_type_id, name" + _check_pms_properties_auto = True + + name = fields.Char( + string="Room Name", + help="Room Name", + required=True, + ) + active = fields.Boolean( + string="Active", help="Determines if room is active", default=True + ) + sequence = fields.Integer( + string="Sequence", + help="Field used to change the position of the rooms in tree view." + "Changing the position changes the sequence", + default=0, + ) + pms_property_id = fields.Many2one( + string="Property", + help="Properties with access to the element;" + " if not set, all properties can access", + required=True, + default=lambda self: self.env.user.get_active_property_ids()[0], + comodel_name="pms.property", + index=True, + ondelete="restrict", + ) + room_type_id = fields.Many2one( + string="Property Room Type", + help="Unique room type for the rooms", + required=True, + comodel_name="pms.room.type", + ondelete="restrict", + index=True, + check_pms_properties=True, + ) + parent_id = fields.Many2one( + string="Parent Room", + help="Indicates that this room is a child of another room", + comodel_name="pms.room", + ondelete="restrict", + index=True, + check_pms_properties=True, + ) + child_ids = fields.One2many( + string="Child Rooms", + help="Child rooms of the room", + comodel_name="pms.room", + inverse_name="parent_id", + check_pms_properties=True, + ) + ubication_id = fields.Many2one( + string="Ubication", + help="At which ubication the room is located.", + comodel_name="pms.ubication", + index=True, + check_pms_properties=True, + ) + capacity = fields.Integer( + string="Capacity", help="The maximum number of people that can occupy a room" + ) + extra_beds_allowed = fields.Integer( + string="Extra Beds Allowed", + help="Number of extra beds allowed in room", + required=True, + default="0", + ) + room_amenity_ids = fields.Many2many( + string="Room Amenities", + help="List of amenities included in room", + comodel_name="pms.amenity", + relation="pms_room_amenity_rel", + column1="room_id", + column2="amenity_id", + check_pms_properties=True, + ) + is_shared_room = fields.Boolean( + string="Is a Shared Room", + help="allows you to reserve units " " smaller than the room itself (eg beds)", + compute="_compute_is_shared_room", + readonly=False, + store=True, + ) + description_sale = fields.Text( + string="Sale Description", + help="A description of the Product that you want to communicate to " + " your customers. This description will be copied to every Sales " + " Order, Delivery Order and Customer Invoice/Credit Note", + translate=True, + ) + + short_name = fields.Char( + string="Short Name", + help="Four character name, if not set, autocompletes with the first two letters of " + "the room name and two incremental numbers", + ) + + _sql_constraints = [ + ( + "room_property_unique", + "unique(name, pms_property_id)", + "You cannot have more than one room " + "with the same name in the same property", + ), + ( + "room_short_name_unique", + "unique(short_name, pms_property_id)", + "You cannot have more than one room " + "with the same short name in the same property", + ), + ] + + @api.depends("child_ids") + def _compute_is_shared_room(self): + for record in self: + if record.child_ids: + record.is_shared_room = True + elif not record.is_shared_room: + record.is_shared_room = False + + def name_get(self): + result = [] + for room in self: + name = room.name + if room.room_type_id: + name += " [%s]" % room.room_type_id.default_code + if room.room_amenity_ids: + for amenity in room.room_amenity_ids: + if amenity.is_add_code_room_name: + name += " %s" % amenity.default_code + result.append((room.id, name)) + return result + + # Constraints and onchanges + @api.constrains("capacity") + def _check_capacity(self): + for record in self: + if record.capacity < 1: + raise ValidationError( + _( + "The capacity of the \ + room must be greater than 0." + ) + ) + + @api.constrains("is_shared_room") + def _check_shared_room(self): + for record in self: + if record.is_shared_room and not record.child_ids: + raise ValidationError( + _( + "The reservation units are required \ + on shared rooms." + ) + ) + + @api.model + def _check_adults(self, reservation, service_line_ids=False): + for line in reservation.reservation_line_ids: + num_extra_beds = 0 + if service_line_ids: + extra_beds = service_line_ids.filtered( + lambda x: x.date == line.date and x.product_id.is_extra_bed is True + ) + num_extra_beds = sum(extra_beds.mapped("day_qty")) if extra_beds else 0 + if line.room_id: + if ( + reservation.adults + reservation.children_occupying + ) > line.room_id.get_capacity(num_extra_beds): + raise ValidationError( + _( + "Persons can't be higher than room capacity (%s)", + reservation.name, + ) + ) + + @api.constrains("short_name") + def _check_short_name(self): + for record in self: + if len(record.short_name) > 4: + raise ValidationError( + _("The short name can't contain more than 4 characters") + ) + + @api.model + def create(self, vals): + if vals.get("name") and not vals.get("short_name"): + if len(vals["name"]) > 4: + short_name = self.calculate_short_name(vals) + vals.update({"short_name": short_name}) + else: + vals.update({"short_name": vals["name"]}) + return super(PmsRoom, self).create(vals) + + def write(self, vals): + if vals.get("name") and not vals.get("short_name"): + if len(vals["name"]) > 4: + short_name = self.calculate_short_name(vals) + vals.update({"short_name": short_name}) + else: + vals.update({"short_name": vals["name"]}) + return super(PmsRoom, self).write(vals) + + def calculate_short_name(self, vals): + short_name = vals["name"][:2].upper() + pms_property_id = self.pms_property_id.id + if vals.get("pms_property_id"): + pms_property_id = vals["pms_property_id"] + rooms = self.env["pms.room"].search([("pms_property_id", "=", pms_property_id)]) + same_name_rooms = rooms.filtered( + lambda room: room.name[:2].upper() == short_name + ) + numbers_name = [0] + for room in same_name_rooms: + if room.short_name and room.short_name[:2] == short_name: + if all(character.isdigit() for character in room.short_name[2:4]): + numbers_name.append(int(room.short_name[2:4])) + max_number = max(numbers_name) + 1 + if max_number < 10: + max_number = str(max_number).zfill(2) + short_name += str(max_number) + return str(short_name) + + # Business methods + + def get_capacity(self, extra_bed=0): + for record in self: + if extra_bed > record.extra_beds_allowed: + raise ValidationError( + _("Extra beds can't be greater than allowed beds for this room") + ) + return record.capacity + extra_bed diff --git a/pms/models/pms_room_closure_reason.py b/pms/models/pms_room_closure_reason.py new file mode 100644 index 0000000000..a1135c623a --- /dev/null +++ b/pms/models/pms_room_closure_reason.py @@ -0,0 +1,30 @@ +# Copyright 2017 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class RoomClosureReason(models.Model): + _name = "room.closure.reason" + _description = "Cause of out of service" + + name = fields.Char( + string="Name", + help="The name that identifies the room closure reason", + required=True, + translate=True, + ) + pms_property_ids = fields.Many2many( + string="Properties", + help="Properties with access to the element;" + " if not set, all properties can access", + comodel_name="pms.property", + relation="pms_room_closure_reason_pms_property_rel", + column1="room_closure_reason_type_id", + column2="pms_property_id", + ondelete="restrict", + ) + description = fields.Text( + string="Description", + help="Explanation of the reason for closing a room", + translate=True, + ) diff --git a/pms/models/pms_room_type.py b/pms/models/pms_room_type.py new file mode 100644 index 0000000000..ed9f571ccc --- /dev/null +++ b/pms/models/pms_room_type.py @@ -0,0 +1,220 @@ +# Copyright 2017 Alexandre Díaz +# Copyright 2017 Dario Lodeiros +# Copyright 2021 Eric Antones +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class PmsRoomType(models.Model): + """Before creating a 'room type', you need to consider the following: + With the term 'room type' is meant a sales type of residential accommodation: for + example, a Double Room, a Economic Room, an Apartment, a Tent, a Caravan... + """ + + _name = "pms.room.type" + _description = "Room Type" + _inherits = {"product.product": "product_id"} + _order = "sequence,default_code,name" + _check_pms_properties_auto = True + + sequence = fields.Integer( + string="Sequence", + help="Field used to change the position of the room types in tree view.", + default=0, + ) + product_id = fields.Many2one( + string="Product Room Type", + help="Product identifier associated with room type", + comodel_name="product.product", + required=True, + delegate=True, + index=True, + ondelete="cascade", + ) + room_ids = fields.One2many( + string="Rooms", + help="Rooms that belong to room type.", + comodel_name="pms.room", + inverse_name="room_type_id", + check_pms_properties=True, + ) + class_id = fields.Many2one( + string="Property Type Class", + help="Class to which the room type belongs", + comodel_name="pms.room.type.class", + required=True, + index=True, + check_pms_properties=True, + ) + board_service_room_type_ids = fields.One2many( + string="Board Services", + help="Board Service included in room type", + comodel_name="pms.board.service.room.type", + inverse_name="pms_room_type_id", + check_pms_properties=True, + ) + room_amenity_ids = fields.Many2many( + string="Room Type Amenities", + help="List of amenities included in room type", + comodel_name="pms.amenity", + relation="pms_room_type_amenity_rel", + column1="room_type_id", + column2="amenity_id", + check_pms_properties=True, + ) + default_code = fields.Char( + string="Code", + help="Identification code for a room type", + required=True, + ) + total_rooms_count = fields.Integer( + string="Total Rooms Count", + help="The number of rooms in a room type", + compute="_compute_total_rooms_count", + store=True, + ) + default_max_avail = fields.Integer( + string="Default Max. Availability", + help="Maximum simultaneous availability on own Booking Engine " + "given no availability rules. " + "Use `-1` for using maximum simultaneous availability.", + default=-1, + ) + default_quota = fields.Integer( + string="Default Quota", + help="Quota assigned to the own Booking Engine given no availability rules. " + "Use `-1` for managing no quota.", + default=-1, + ) + overnight_room = fields.Boolean( + related="class_id.overnight", + store=True, + ) + min_price = fields.Float( + string="Min. Price", + help="Minimum price for a room type", + default=5.0, + ) + + def name_get(self): + result = [] + for room_type in self: + name = room_type.name + if self._context.get("checkin") and self._context.get("checkout"): + pms_property = self.env["pms.property"].browse( + self._context.get("pms_property_id") + ) + pms_property = pms_property.with_context( + checkin=self._context.get("checkin"), + checkout=self._context.get("checkout"), + room_type_id=room_type.id, + pricelist_id=self._context.get("pricelist_id") or False, + ) + avail = pms_property.availability + name += " (%s)" % avail + result.append((room_type.id, name)) + return result + + @api.depends("room_ids", "room_ids.active") + def _compute_total_rooms_count(self): + for record in self: + record.total_rooms_count = len(record.room_ids) + + @api.model + def get_room_types_by_property(self, pms_property_id, default_code=None): + """ + :param pms_property_id: property ID + :param default_code: room type code (optional) + :return: - recordset of + - all the pms.room.type of the pms_property_id + if default_code not defined + - one or 0 pms.room.type if default_code defined + - ValidationError if more than one default_code found by + the same pms_property_id + """ + domain = [] + if default_code: + domain += ["&", ("default_code", "=", default_code)] + company_id = self.env["pms.property"].browse(pms_property_id).company_id.id + domain += [ + "|", + ("pms_property_ids", "in", pms_property_id), + "|", + "&", + ("pms_property_ids", "=", False), + ("company_id", "=", company_id), + "&", + ("pms_property_ids", "=", False), + ("company_id", "=", False), + ] + records = self.search(domain) + res, res_priority = {}, {} + for rec in records: + res_priority.setdefault(rec.default_code, -1) + priority = (rec.pms_property_ids and 2) or (rec.company_id and 1 or 0) + if priority > res_priority[rec.default_code]: + res.setdefault(rec.default_code, rec.id) + res[rec.default_code], res_priority[rec.default_code] = rec.id, priority + elif priority == res_priority[rec.default_code]: + raise ValidationError( + _( + "Integrity error: There's multiple room types " + "with the same code %s and properties" + ) + % rec.default_code + ) + return self.browse(list(res.values())) + + @api.constrains("default_code", "pms_property_ids", "company_id") + def _check_code_property_company_uniqueness(self): + msg = _("Already exists another room type with the same code and properties") + for rec in self: + if not rec.pms_property_ids: + if self.search( + [ + ("id", "!=", rec.id), + ("default_code", "=", rec.default_code), + ("pms_property_ids", "=", False), + ("company_id", "=", rec.company_id.id), + ] + ): + raise ValidationError(msg) + else: + for pms_property in rec.pms_property_ids: + other = rec.get_room_types_by_property( + pms_property.id, rec.default_code + ) + if other and other != rec: + raise ValidationError(msg) + + # ORM Overrides + # TODO: Review Check product fields default values to room + @api.model + def create(self, vals): + """Add room types as not purchase services.""" + vals.update( + { + "purchase_ok": False, + "sale_ok": False, + "detailed_type": "service", + } + ) + return super().create(vals) + + # def unlink(self): + # for record in self: + # record.product_id.unlink() + # return super().unlink() + + def get_room_type_capacity(self, pms_property_id): + """ + :param pms_property_id: int + :return: the minimum capacity for rooms of current type + """ + self.ensure_one() + capacities = self.room_ids.filtered( + lambda r: not r.pms_property_id or r.pms_property_id.id == pms_property_id + ).mapped("capacity") + return min(capacities) if any(capacities) else 0 diff --git a/pms/models/pms_room_type_class.py b/pms/models/pms_room_type_class.py new file mode 100644 index 0000000000..6c4a07e65c --- /dev/null +++ b/pms/models/pms_room_type_class.py @@ -0,0 +1,127 @@ +# Copyright 2017 Alexandre Díaz +# Copyright 2017 Dario Lodeiros +# Copyright 2021 Eric Antones +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class PmsRoomTypeClass(models.Model): + """Before creating a 'room type_class', you need to consider the following: + With the term 'room type class' is meant a physical class of + residential accommodation: for example, a Room, a Bed, an Apartment, + a Tent, a Caravan... + """ + + _name = "pms.room.type.class" + _description = "Room Type Class" + _order = "sequence, name, default_code" + _check_pms_properties_auto = True + + name = fields.Char( + string="Class Name", + help="Name of the room type class", + required=True, + translate=True, + ) + active = fields.Boolean( + string="Active", + help="If unchecked, it will allow you to hide the room type", + default=True, + ) + sequence = fields.Integer( + string="Sequence", + help="Field used to change the position of the room type classes in tree view.", + default=0, + ) + pms_property_ids = fields.Many2many( + string="Properties", + help="Properties with access to the element;" + " if not set, all properties can access", + comodel_name="pms.property", + relation="pms_room_type_class_property_rel", + column1="room_type_class_id", + column2="pms_property_id", + ondelete="restrict", + check_pms_properties=True, + ) + room_type_ids = fields.One2many( + string="Types", + help="Room Types that belong to this Room Type Class", + comodel_name="pms.room.type", + inverse_name="class_id", + check_pms_properties=True, + ) + default_code = fields.Char( + string="Code", + help="Room type class identification code", + required=True, + ) + overnight = fields.Boolean( + string="Use for overnight stays", + help="Set False if if these types of spaces are not used for overnight stays", + default=True, + ) + + @api.model + def get_unique_by_property_code(self, pms_property_id, default_code=None): + """ + :param pms_property_id: property ID + :param default_code: room type code (optional) + :return: - recordset of + - all the pms.room.type.class of the pms_property_id + if default_code not defined + - one or 0 pms.room.type.class if default_code defined + - ValidationError if more than one default_code found by + the same pms_property_id + """ + # TODO: similiar code as room.type -> unify + domain = [] + if default_code: + domain += ["&", ("default_code", "=", default_code)] + domain += [ + "|", + ("pms_property_ids", "in", pms_property_id), + ("pms_property_ids", "=", False), + ] + records = self.search(domain) + res, res_priority = {}, {} + for rec in records: + res_priority.setdefault(rec.default_code, -1) + priority = rec.pms_property_ids and 1 or 0 + if priority > res_priority[rec.default_code]: + res.setdefault(rec.default_code, rec.id) + res[rec.default_code], res_priority[rec.default_code] = rec.id, priority + elif priority == res_priority[rec.default_code]: + raise ValidationError( + _( + "Integrity error: There's multiple room types " + "with the same code %s and properties" + ) + % rec.default_code + ) + return self.browse(list(res.values())) + + @api.constrains("default_code", "pms_property_ids") + def _check_code_property_uniqueness(self): + # TODO: similiar code as room.type -> unify + msg = _( + "Already exists another room type class with the same code and properties" + ) + for rec in self: + if not rec.pms_property_ids: + if self.search( + [ + ("id", "!=", rec.id), + ("default_code", "=", rec.default_code), + ("pms_property_ids", "=", False), + ] + ): + raise ValidationError(msg) + else: + for pms_property in rec.pms_property_ids: + other = rec.get_unique_by_property_code( + pms_property.id, rec.default_code + ) + if other and other != rec: + raise ValidationError(msg) diff --git a/pms/models/pms_sale_channel.py b/pms/models/pms_sale_channel.py new file mode 100644 index 0000000000..17b995dd85 --- /dev/null +++ b/pms/models/pms_sale_channel.py @@ -0,0 +1,42 @@ +from odoo import fields, models + + +class PmsSaleChannel(models.Model): + _name = "pms.sale.channel" + _description = "Sales Channel" + _check_pms_properties_auto = True + + name = fields.Text(string="Sale Channel Name", help="The name of the sale channel") + channel_type = fields.Selection( + string="Sale Channel Type", + help="Type of sale channel; it can be 'direct'(if there is" + "no intermediary) or 'indirect'(if there are" + "intermediaries between partner and property", + selection=[("direct", "Direct"), ("indirect", "Indirect")], + ) + is_on_line = fields.Boolean( + string="On Line", help="Indicates if the sale channel is on-line" + ) + product_pricelist_ids = fields.Many2many( + string="Pricelists", + help="Pricelists for a sale channel", + comodel_name="product.pricelist", + relation="pms_sale_channel_product_pricelist_rel", + column1="pms_sale_channel_id", + column2="product_pricelist_id", + check_pms_properties=True, + domain="[('is_pms_available', '=', True)]", + ) + pms_property_ids = fields.Many2many( + string="Properties", + help="Properties with access to the element;" + " if not set, all properties can access", + required=False, + ondelete="restrict", + comodel_name="pms.property", + relation="pms_sale_channel_pms_property_rel", + column1="pms_sale_channel_id", + column2="pms_property_id", + check_pms_properties=True, + ) + icon = fields.Image(string="Logo", max_width=1024, max_height=1024, store=True) diff --git a/pms/models/pms_service.py b/pms/models/pms_service.py new file mode 100644 index 0000000000..d1c0202fec --- /dev/null +++ b/pms/models/pms_service.py @@ -0,0 +1,649 @@ +# Copyright 2017 Alexandre Díaz +# Copyright 2017 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from datetime import timedelta + +from odoo import _, api, fields, models + + +class PmsService(models.Model): + _name = "pms.service" + _description = "Services and its charges" + _check_pms_properties_auto = True + + name = fields.Char( + string="Service description", + help="Service description", + readonly=False, + store=True, + compute="_compute_name", + ) + product_id = fields.Many2one( + string="Service", + help="Product associated with this service", + required=True, + index=True, + comodel_name="product.product", + ondelete="restrict", + check_pms_properties=True, + domain="[(is_pms_available, '=', True)]", + ) + folio_id = fields.Many2one( + string="Folio", + help="Folio in which the service is included", + readonly=False, + store=True, + index=True, + comodel_name="pms.folio", + compute="_compute_folio_id", + check_pms_properties=True, + ) + sale_line_ids = fields.One2many( + string="Sale Lines", + help="", + copy=False, + comodel_name="folio.sale.line", + inverse_name="service_id", + check_pms_properties=True, + ) + reservation_id = fields.Many2one( + string="Room", + help="Reservation in which the service is included", + default=lambda self: self._default_reservation_id(), + comodel_name="pms.reservation", + ondelete="cascade", + index=True, + check_pms_properties=True, + ) + service_line_ids = fields.One2many( + string="Service Lines", + help="Subservices included in this service", + readonly=False, + store=True, + comodel_name="pms.service.line", + inverse_name="service_id", + compute="_compute_service_line_ids", + check_pms_properties=True, + ) + company_id = fields.Many2one( + string="Company", + help="Company to which the service belongs", + readonly=True, + store=True, + index=True, + related="folio_id.company_id", + ) + pms_property_id = fields.Many2one( + string="Property", + help="Property to which the service belongs", + readonly=True, + store=True, + index=True, + comodel_name="pms.property", + related="folio_id.pms_property_id", + check_pms_properties=True, + ) + tax_ids = fields.Many2many( + string="Taxes", + help="Taxes applied in the service", + readonly=False, + store=True, + comodel_name="account.tax", + domain=["|", ("active", "=", False), ("active", "=", True)], + compute="_compute_tax_ids", + ) + # analytic_tag_ids = fields.Many2many( + # string="Analytic Tags", + # help="", + # comodel_name="account.analytic.tag", + # ) + currency_id = fields.Many2one( + string="Currency", + help="The currency used in relation to the folio", + readonly=True, + store=True, + index=True, + related="folio_id.currency_id", + ) + sequence = fields.Integer(string="Sequence", help="", default=10) + state = fields.Selection( + string="State", + help="Service status, it corresponds with folio status", + related="folio_id.state", + store=True, + ) + per_day = fields.Boolean( + string="Per Day", + help="Indicates if service is sold by days", + related="product_id.per_day", + related_sudo=True, + ) + product_qty = fields.Integer( + string="Quantity", + help="Number of services that were sold", + readonly=False, + store=True, + compute="_compute_product_qty", + ) + is_board_service = fields.Boolean( + string="Is Board Service", + help="Indicates if the service is part of a board service", + ) + board_service_line_id = fields.Many2one( + string="Board Service Line", + help="Board Service Line in which this service is included", + comodel_name="pms.board.service.room.type.line", + check_pms_properties=True, + ) + # Non-stored related field to allow portal user to + # see the image of the product he has ordered + product_image = fields.Binary( + string="Product Image", + help="Image of the service", + store=False, + related="product_id.image_1024", + related_sudo=True, + ) + invoice_status = fields.Selection( + string="Invoice Status", + help="State in which the service is with respect to invoices." + "It can be 'invoiced', 'to_invoice' or 'no'", + readonly=True, + default="no", + store=True, + compute="_compute_invoice_status", + selection=[ + ("invoiced", "Fully Invoiced"), + ("to_invoice", "To Invoice"), + ("no", "Nothing to Invoice"), + ], + ) + sale_channel_origin_id = fields.Many2one( + string="Sale Channel Origin", + help="Sale Channel through which service was created, the original", + comodel_name="pms.sale.channel", + check_pms_properties=True, + index=True, + ) + price_subtotal = fields.Monetary( + string="Subtotal", + help="Subtotal price without taxes", + readonly=True, + store=True, + compute="_compute_amount_service", + ) + price_total = fields.Monetary( + string="Total", + help="Total price with taxes", + readonly=True, + store=True, + compute="_compute_amount_service", + ) + price_tax = fields.Float( + string="Taxes Amount", + help="Total of taxes in service", + readonly=True, + store=True, + compute="_compute_amount_service", + ) + + discount = fields.Float( + string="Discount (€/ud)", + help="Discount of total price", + readonly=False, + store=True, + digits=("Discount"), + compute="_compute_discount", + inverse="_inverse_discount", + ) + no_auto_add_lines = fields.Boolean( + string="Force No Auto Add Lines", + help="""Technical field to avoid add service lines to service + automatically when creating a new service. It is used when + creating a new service with lines in vals + """, + default=False, + ) + default_invoice_to = fields.Many2one( + string="Invoice to", + help="""Indicates the contact to which this line will be + billed by default, if it is not established, + a guest or the generic contact will be used instead""", + readonly=False, + store=True, + index=True, + compute="_compute_default_invoice_to", + comodel_name="res.partner", + ondelete="restrict", + ) + is_cancel_penalty = fields.Boolean( + string="Is Cancel Penalty", + help="Indicates if the service is a cancel penalty", + readonly=True, + compute="_compute_is_cancel_penalty", + ) + + # Compute and Search methods + @api.depends("product_id") + def _compute_tax_ids(self): + for service in self: + service.tax_ids = service.product_id.taxes_id.filtered( + lambda r: not service.company_id or r.company_id == service.company_id + ) + + @api.depends("service_line_ids", "service_line_ids.day_qty") + def _compute_product_qty(self): + self.product_qty = 0 + for service in self.filtered("service_line_ids"): + qty = sum(service.service_line_ids.mapped("day_qty")) + service.product_qty = qty + + @api.depends("reservation_id", "reservation_id.folio_id") + def _compute_folio_id(self): + for record in self: + if record.reservation_id: + record.folio_id = record.reservation_id.folio_id + elif not record.folio_id: + record.folio_id = False + + @api.depends( + "sale_line_ids", + "sale_line_ids.invoice_status", + ) + def _compute_invoice_status(self): + """ + Compute the invoice status of a Reservation. Possible statuses: + Base on folio sale line invoice status + """ + for line in self: + states = list(set(line.sale_line_ids.mapped("invoice_status"))) + if len(states) == 1: + line.invoice_status = states[0] + elif len(states) >= 1: + if "to_invoice" in states: + line.invoice_status = "to_invoice" + elif "invoiced" in states: + line.invoice_status = "invoiced" + else: + line.invoice_status = "no" + else: + line.invoice_status = "no" + + @api.depends("service_line_ids.price_day_total") + def _compute_amount_service(self): + for service in self: + if service.service_line_ids: + service.update( + { + "price_tax": sum( + service.service_line_ids.mapped("price_day_tax") + ), + "price_total": sum( + service.service_line_ids.mapped("price_day_total") + ), + "price_subtotal": sum( + service.service_line_ids.mapped("price_day_subtotal") + ), + } + ) + else: + service.update( + { + "price_tax": 0, + "price_total": 0, + "price_subtotal": 0, + } + ) + + @api.depends("product_id") + def _compute_name(self): + self.name = False + for service in self.filtered("product_id"): + product = service.product_id.with_context( + lang=service.folio_id.partner_id.lang + if service.folio_id.partner_id + else self.env.context.get("lang", "es_ES"), + partner=service.folio_id.partner_id.id, + ) + title = False + message = False + warning = {} + if product.sale_line_warn != "no-message": + title = _("Warning for %s") % product.name + message = product.sale_line_warn_msg + warning["title"] = title + warning["message"] = message + result = {"warning": warning} + if product.sale_line_warn == "block": + self.product_id = False + return result + name = product.name_get()[0][1] + if product.description_sale: + name += "\n" + product.description_sale + service.name = name + + @api.depends( + "reservation_id.reservation_line_ids", + "product_id", + "reservation_id.adults", + "product_qty", + ) + # flake8:noqa=C901 + def _compute_service_line_ids(self): + for service in self: + if service.no_auto_add_lines: + continue + if service.product_id: + day_qty = service.product_qty or 1 + if service.reservation_id and service.product_id: + reservation = service.reservation_id + # REVIEW: review method dependencies, reservation_line_ids + # instead of checkin/checkout + if not reservation.checkin or not reservation.checkout: + if not service.service_line_ids: + service.service_line_ids = False + continue + product = service.product_id + consumed_on = product.consumed_on + if product.per_day: + lines = [] + day_qty = service._service_day_qty() + days_diff = (reservation.checkout - reservation.checkin).days + for i in range(0, days_diff): + if consumed_on == "after": + i += 1 + idate = reservation.checkin + timedelta(days=i) + old_line = service.service_line_ids.filtered( + lambda r: r.date == idate + ) + price_unit = service._get_price_unit_line(idate) + if old_line and old_line.auto_qty: + lines.append( + ( + 1, + old_line.id, + { + "day_qty": day_qty, + "auto_qty": True, + }, + ) + ) + elif not old_line: + lines.append( + ( + 0, + False, + { + "date": idate, + "day_qty": day_qty, + "auto_qty": True, + "price_unit": price_unit, + }, + ) + ) + move_day = 0 + if consumed_on == "after": + move_day = 1 + for del_service_id in service.service_line_ids.filtered_domain( + [ + "|", + ( + "date", + "<", + reservation.checkin + timedelta(move_day), + ), + ( + "date", + ">=", + reservation.checkout + timedelta(move_day), + ), + ] + ).ids: + lines.append((2, del_service_id)) + # TODO: check intermediate states in check_adults restriction + # when lines are removed + service.service_line_ids = lines + else: + if not service.service_line_ids: + price_unit = service._get_price_unit_line() + service.service_line_ids = [ + ( + 0, + False, + { + "date": fields.Date.today(), + "day_qty": day_qty, + "price_unit": price_unit, + }, + ) + ] + # if service lines has only one line, + # set its day_qty to service product_qty + elif len(service.service_line_ids) == 1 and service.product_qty: + service.service_line_ids.day_qty = service.product_qty + + else: + if not service.service_line_ids: + price_unit = service._get_price_unit_line() + service.service_line_ids = [ + ( + 0, + False, + { + "date": fields.Date.today(), + "day_qty": day_qty, + "price_unit": price_unit, + }, + ) + ] + else: + service.service_line_ids = False + + @api.depends("service_line_ids.cancel_discount") + def _compute_discount(self): + for record in self: + discount = 0 + for line in record.service_line_ids: + amount = line.day_qty * line.price_unit + first_discount = amount * ((line.discount or 0.0) * 0.01) + price = amount - first_discount + cancel_discount = price * ((line.cancel_discount or 0.0) * 0.01) + discount += first_discount + cancel_discount + record.discount = discount + + def _inverse_discount(self): + # compute the discount line percentage + # based on the discount amount + for record in self: + for line in record.service_line_ids: + line.discount = record.discount + line.cancel_discount = 0 + + @api.depends("sale_channel_origin_id", "folio_id.agency_id") + def _compute_default_invoice_to(self): + for record in self: + agency = record.folio_id.agency_id + if ( + agency + and agency.invoice_to_agency == "always" + and agency.sale_channel_id == record.sale_channel_origin_id + ): + record.default_invoice_to = agency + elif not record.default_invoice_to: + record.default_invoice_to = False + + def _compute_is_cancel_penalty(self): + for record in self: + if record.product_id == record.company_id.cancel_penalty_product_id: + record.is_cancel_penalty = True + else: + record.is_cancel_penalty = False + + def name_get(self): + result = [] + for rec in self: + name = [] + name.append("{name}".format(name=rec.name)) + if rec.reservation_id.name: + name.append("{name}".format(name=rec.reservation_id.name)) + result.append((rec.id, ", ".join(name))) + return result + + @api.model + def _default_reservation_id(self): + if self.env.context.get("reservation_ids"): + ids = [item[1] for item in self.env.context["reservation_ids"]] + return self.env["pms.reservation"].browse([(ids)], limit=1) + elif self.env.context.get("default_reservation_id"): + return self.env.context.get("default_reservation_id") + return False + + # Action methods + def open_service_ids(self): + action = self.env.ref("pms.action_pms_services_form").sudo().read()[0] + action["views"] = [(self.env.ref("pms.pms_service_view_form").id, "form")] + action["res_id"] = self.id + action["target"] = "new" + return action + + # ORM Overrides + @api.model + def name_search(self, name="", args=None, operator="ilike", limit=100): + if args is None: + args = [] + if not (name == "" and operator == "ilike"): + args += [ + "|", + ("reservation_id.name", operator, name), + ("name", operator, name), + ] + return super(PmsService, self).name_search( + name="", args=args, operator="ilike", limit=limit + ) + + def _get_display_price(self, product): + folio = self.folio_id + reservation = self.reservation_id + origin = folio if folio else reservation + return self.env["product.product"]._pms_display_price( + pricelist_id=origin.pricelist_id.id, + product_id=product.id, + company_id=origin.company_id.id, + product_qty=self.product_qty or 1.0, + partner_id=origin.partner_id.id if origin.partner_id else False, + ) + + # Businness Methods + def _service_day_qty(self): + self.ensure_one() + qty = self.product_qty if len(self.service_line_ids) == 1 else 1 + if not self.reservation_id: + return qty + # TODO: Pass per_person to service line from product default_per_person + # When the user modifies the quantity avoid overwriting + if self.product_id.per_person: + if self.is_board_service: + qty = ( + self.reservation_id.adults + if self.board_service_line_id.adults + else 0 + ) + qty += ( + self.reservation_id.children + if self.board_service_line_id.children + else 0 + ) + else: + qty = self.reservation_id.adults + return qty + + def _get_price_unit_line(self, date=False): + self.ensure_one() + if self.reservation_id.reservation_type in ("normal", "staff"): + folio = self.folio_id + reservation = self.reservation_id + origin = reservation if reservation else folio + if origin: + partner = origin.partner_id + pricelist = origin.pricelist_id + board_room_type = False + product_context = dict( + self.env.context, + lang=partner.lang, + partner=partner.id, + quantity=self.product_qty, + date=folio.date_order if folio else fields.Date.today(), + pricelist=pricelist.id, + board_service=board_room_type.id if board_room_type else False, + uom=self.product_id.uom_id.id, + fiscal_position=False, + property=origin.pms_property_id.id, + ) + if date: + product_context["consumption_date"] = date + if reservation and self.is_board_service: + product_context[ + "board_service" + ] = reservation.board_service_room_id.id + product = self.product_id.with_context(product_context) + return self.env["account.tax"]._fix_tax_included_price_company( + self.env["product.product"]._pms_get_display_price( + pricelist_id=pricelist.id, + product=product, + company_id=origin.company_id.id, + product_qty=self.product_qty, + partner_id=partner.id, + ), + product.taxes_id, + self.tax_ids, + origin.company_id, + ) + else: + return 0 + + @api.model + def create(self, vals): + if vals.get("reservation_id") and not vals.get("sale_channel_origin_id"): + reservation = self.env["pms.reservation"].browse(vals["reservation_id"]) + if reservation.sale_channel_origin_id: + vals["sale_channel_origin_id"] = reservation.sale_channel_origin_id.id + elif ( + vals.get("folio_id") + and not vals.get("reservation_id") + and not vals.get("sale_channel_origin_id") + ): + folio = self.env["pms.folio"].browse(vals["folio_id"]) + if folio.sale_channel_origin_id: + vals["sale_channel_origin_id"] = folio.sale_channel_origin_id.id + record = super(PmsService, self).create(vals) + return record + + def write(self, vals): + folios_to_update_channel = self.env["pms.folio"] + lines_to_update_channel = self.env["pms.service.line"] + if "sale_channel_origin_id" in vals: + folios_to_update_channel = self.get_folios_to_update_channel(vals) + res = super(PmsService, self).write(vals) + if folios_to_update_channel: + folios_to_update_channel.sale_channel_origin_id = vals[ + "sale_channel_origin_id" + ] + if lines_to_update_channel: + lines_to_update_channel.sale_channel_id = vals["sale_channel_origin_id"] + return res + + def get_folios_to_update_channel(self, vals): + folios_to_update_channel = self.env["pms.folio"] + for folio in self.mapped("folio_id"): + if ( + any( + service.sale_channel_origin_id == folio.sale_channel_origin_id + for service in self.filtered(lambda r: r.folio_id == folio) + ) + and vals["sale_channel_origin_id"] != folio.sale_channel_origin_id.id + and (len(folio.reservation_ids) == 0) + and (len(folio.service_ids) == 1) + ): + folios_to_update_channel += folio + return folios_to_update_channel diff --git a/pms/models/pms_service_line.py b/pms/models/pms_service_line.py new file mode 100644 index 0000000000..797df498a4 --- /dev/null +++ b/pms/models/pms_service_line.py @@ -0,0 +1,264 @@ +# Copyright 2017-2018 Alexandre Díaz +# Copyright 2017 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import datetime +import logging + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + +_logger = logging.getLogger(__name__) + + +class PmsServiceLine(models.Model): + _name = "pms.service.line" + _description = "Service by day" + _order = "date" + _rec_name = "service_id" + _check_pms_properties_auto = True + + service_id = fields.Many2one( + string="Service Room", + help="Service identifier", + required=True, + copy=False, + index=True, + comodel_name="pms.service", + ondelete="cascade", + ) + is_board_service = fields.Boolean( + string="Is Board Service", + help="Indicates if the service line is part of a board service", + store=True, + related="service_id.is_board_service", + ) + product_id = fields.Many2one( + string="Product", + help="Product associated with this service line", + store=True, + index=True, + related="service_id.product_id", + check_pms_properties=True, + ) + tax_ids = fields.Many2many( + string="Taxes", + help="Taxes applied in the service line", + readonly="True", + comodel_name="account.tax", + related="service_id.tax_ids", + ) + pms_property_id = fields.Many2one( + string="Property", + help="Property to which the service belongs", + readonly=True, + store=True, + index=True, + comodel_name="pms.property", + related="service_id.pms_property_id", + check_pms_properties=True, + ) + sale_line_ids = fields.Many2many( + string="Sales Lines", + readonly=True, + copy=False, + comodel_name="folio.sale.line", + check_pms_properties=True, + ) + date = fields.Date( + string="Date", + help="Sate on which the product is to be consumed", + ) + day_qty = fields.Integer( + string="Units", + help="Amount to be consumed per day", + ) + price_unit = fields.Float( + string="Unit Price", + help="Price per unit of service", + digits=("Product Price"), + ) + price_day_subtotal = fields.Monetary( + string="Subtotal", + help="Subtotal price without taxes", + readonly=True, + store=True, + compute="_compute_day_amount_service", + ) + price_day_total = fields.Monetary( + string="Total", + help="Total price with taxes", + readonly=True, + store=True, + compute="_compute_day_amount_service", + ) + price_day_tax = fields.Float( + string="Taxes Amount", + help="", + readonly=True, + store=True, + compute="_compute_day_amount_service", + ) + currency_id = fields.Many2one( + string="Currency", + help="The currency used in relation to the service where it's included", + readonly=True, + store=True, + index=True, + related="service_id.currency_id", + ) + reservation_id = fields.Many2one( + string="Reservation", + help="Room to which the services will be applied", + readonly=True, + store=True, + index=True, + related="service_id.reservation_id", + check_pms_properties=True, + ) + discount = fields.Float( + string="Discount (%)", + help="Discount in the price of the service.", + readonly=False, + store=True, + default=0.0, + digits=("Discount"), + compute="_compute_discount", + ) + cancel_discount = fields.Float( + string="Cancelation Discount", + help="", + compute="_compute_cancel_discount", + readonly=True, + store=True, + ) + auto_qty = fields.Boolean( + string="Qty automated setted", + help="Show if the day qty was calculated automatically", + compute="_compute_auto_qty", + readonly=False, + store=True, + ) + default_invoice_to = fields.Many2one( + string="Invoice to", + help="""Indicates the contact to which this line will be + billed by default, if it is not established, + a guest or the generic contact will be used instead""", + comodel_name="res.partner", + store=True, + index=True, + related="service_id.default_invoice_to", + ondelete="restrict", + ) + + @api.depends("day_qty", "discount", "price_unit", "tax_ids") + def _compute_day_amount_service(self): + for line in self: + amount_service = line.price_unit + if amount_service > 0: + currency = line.service_id.currency_id + product = line.product_id + price = amount_service * (1 - (line.discount or 0.0) * 0.01) + # REVIEW: line.day_qty is not the total qty (the total is on service_id) + taxes = line.tax_ids.compute_all( + price, currency, line.day_qty, product=product + ) + line.update( + { + "price_day_tax": sum( + t.get("amount", 0.0) for t in taxes.get("taxes", []) + ), + "price_day_total": taxes["total_included"], + "price_day_subtotal": taxes["total_excluded"], + } + ) + else: + line.update( + { + "price_day_tax": 0, + "price_day_total": 0, + "price_day_subtotal": 0, + } + ) + + @api.depends("service_id.reservation_id", "service_id.reservation_id.discount") + def _compute_discount(self): + """ + On board service the line discount is always + equal to reservation line discount + """ + for record in self: + if not record.discount: + record.discount = 0 + + # TODO: Refact method and allowed cancelled single days + @api.depends("service_id.reservation_id.reservation_line_ids.cancel_discount") + def _compute_cancel_discount(self): + for line in self: + line.cancel_discount = 0 + reservation = line.reservation_id + if reservation.state == "cancel": + if ( + reservation.cancelled_reason + and reservation.pricelist_id + and reservation.pricelist_id.cancelation_rule_id + and reservation.reservation_line_ids.mapped("cancel_discount") + ): + if line.is_board_service: + consumed_date = ( + line.date + if line.product_id.consumed_on == "before" + else line.date + datetime.timedelta(days=-1) + ) + line.cancel_discount = ( + reservation.reservation_line_ids.filtered( + lambda l: l.date == consumed_date + ).cancel_discount + ) + elif not line.service_id.is_cancel_penalty: + line.cancel_discount = 100 + else: + line.cancel_discount = 0 + else: + line.cancel_discount = 0 + + @api.depends("day_qty") + def _compute_auto_qty(self): + """ + Set auto_qty = False if the service is no linked to room or + if the day_qty was set manually + (See autogeneration of service lines in + _compute_service_line_ids -pms.service-) + """ + self.auto_qty = False + + # Constraints and onchanges + @api.constrains("day_qty") + def no_free_resources(self): + for record in self: + limit = record.product_id.daily_limit + if limit > 0: + out_qty = sum( + self.env["pms.service.line"] + .search( + [ + ("product_id", "=", record.product_id.id), + ("date", "=", record.date), + ("service_id", "!=", record.service_id.id), + ] + ) + .mapped("day_qty") + ) + if limit < out_qty + record.day_qty: + raise ValidationError( + _("%s limit exceeded for %s") + % (record.service_id.product_id.name, record.date) + ) + + # Business methods + def _cancel_discount(self): + for record in self: + if record.reservation_id: + day = record.reservation_id.reservation_line_ids.filtered( + lambda d: d.date == record.date + ) + record.cancel_discount = day.cancel_discount diff --git a/pms/models/pms_team_member.py b/pms/models/pms_team_member.py new file mode 100644 index 0000000000..b80cd81206 --- /dev/null +++ b/pms/models/pms_team_member.py @@ -0,0 +1,47 @@ +from odoo import fields, models + + +class PmsTeamMember(models.Model): + _name = "pms.team.member" + _description = "PMS Team Member" + + name = fields.Char( + string="Name", + store=True, + related="user_id.name", + ) + active = fields.Boolean( + string="Active", + default=True, + ) + sequence = fields.Integer( + string="Sequence", + default=10, + ) + pms_property_id = fields.Many2one( + string="Property", + comodel_name="pms.property", + store=True, + index=True, + ondelete="restrict", + ) + user_id = fields.Many2one( + string="User Member", + copy=False, + comodel_name="res.users", + ondelete="restrict", + index=True, + ) + pms_role = fields.Selection( + string="PMS Role", + help="The member role in the organization" + "It can be 'Reception', 'Revenue', 'Administrative', or 'Manager'", + copy=False, + selection=[ + ("reception", "Reception"), + ("revenue", "Revenue"), + ("administrative", "Administrative"), + ("manager", "Operational Manager"), + ], + required=True, + ) diff --git a/pms/models/pms_ubication.py b/pms/models/pms_ubication.py new file mode 100644 index 0000000000..5169eefcd9 --- /dev/null +++ b/pms/models/pms_ubication.py @@ -0,0 +1,50 @@ +# Copyright 2017 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class PmsUbication(models.Model): + _name = "pms.ubication" + _description = "Ubication" + _check_pms_properties_auto = True + + name = fields.Char( + string="Ubication Name", + help="Ubication Name", + required=True, + translate=True, + ) + sequence = fields.Integer( + string="Sequence", + help="Field used to change the position of the ubications in tree view." + "Changing the position changes the sequence", + ) + pms_property_ids = fields.Many2many( + string="Properties", + help="Properties with access to the element;" + " if not set, all properties can access", + comodel_name="pms.property", + relation="pms_ubication_pms_property_rel", + column1="ubication_type_id", + column2="pms_property_id", + ondelete="restrict", + check_pms_properties=True, + ) + pms_room_ids = fields.One2many( + string="Rooms", + help="Rooms found in this location", + comodel_name="pms.room", + inverse_name="ubication_id", + check_pms_properties=True, + ) + + @api.constrains( + "pms_property_ids", + "pms_room_ids", + ) + def _check_property_integrity(self): + for rec in self: + if rec.pms_property_ids and rec.pms_room_ids: + if rec.pms_room_ids.pms_property_id not in rec.pms_property_ids: + raise ValidationError(_("Property not allowed")) diff --git a/pms/models/product_pricelist.py b/pms/models/product_pricelist.py new file mode 100644 index 0000000000..279243809a --- /dev/null +++ b/pms/models/product_pricelist.py @@ -0,0 +1,205 @@ +# Copyright 2017 Alexandre Díaz, Pablo Quesada, Darío Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + +_logger = logging.getLogger(__name__) + + +class ProductPricelist(models.Model): + """Before creating a 'daily' pricelist, you need to consider the following: + A pricelist marked as daily is used as a daily rate plan for room types and + therefore is related only with one property. + """ + + _inherit = "product.pricelist" + _check_pms_properties_auto = True + + # Fields declaration + pms_property_ids = fields.Many2many( + string="Properties", + help="Properties with access to the element;" + " if not set, all properties can access", + required=False, + comodel_name="pms.property", + relation="product_pricelist_pms_property_rel", + column1="product_pricelist_id", + column2="pms_property_id", + ondelete="restrict", + check_pms_properties=True, + ) + company_id = fields.Many2one( + string="Company", + help="Company to which the pricelist belongs", + index=True, + ) + cancelation_rule_id = fields.Many2one( + string="Cancelation Policy", + help="Cancelation Policy included in the room", + comodel_name="pms.cancelation.rule", + index=True, + check_pms_properties=True, + ) + pricelist_type = fields.Selection( + string="Pricelist Type", + help="Pricelist types, it can be Daily Plan", + default="daily", + selection=[("daily", "Daily Plan")], + ) + pms_sale_channel_ids = fields.Many2many( + string="Available Channels", + help="Sale channel for which the pricelist is included", + comodel_name="pms.sale.channel", + check_pms_properties=True, + ) + availability_plan_id = fields.Many2one( + string="Availability Plan", + help="Availability Plan for which the pricelist is included", + comodel_name="pms.availability.plan", + ondelete="restrict", + index=True, + check_pms_properties=True, + ) + item_ids = fields.One2many( + string="Items", + help="Items for which the pricelist is made up", + check_pms_properties=True, + ) + is_pms_available = fields.Boolean( + string="Available in PMS", + help="If the pricelist is available in the PMS", + default=False, + ) + + def _compute_price_rule_get_items( + self, products_qty_partner, date, uom_id, prod_tmpl_ids, prod_ids, categ_ids + ): + board_service = True if self._context.get("board_service") else False + if ( + "property" in self._context + and self._context["property"] + and self._context.get("consumption_date") + ): + self.env.cr.execute( + """ + SELECT item.id + FROM product_pricelist_item item + LEFT JOIN product_category categ + ON item.categ_id = categ.id + LEFT JOIN product_pricelist_pms_property_rel cab + ON item.pricelist_id = cab.product_pricelist_id + LEFT JOIN product_pricelist_item_pms_property_rel lin + ON item.id = lin.product_pricelist_item_id + WHERE (lin.pms_property_id = %s OR lin.pms_property_id IS NULL) + AND (cab.pms_property_id = %s OR cab.pms_property_id IS NULL) + AND (item.product_tmpl_id IS NULL + OR item.product_tmpl_id = ANY(%s)) + AND (item.product_id IS NULL OR item.product_id = ANY(%s)) + AND (item.categ_id IS NULL OR item.categ_id = ANY(%s)) + AND (item.pricelist_id = %s) + AND (item.date_start IS NULL OR item.date_start <=%s) + AND (item.date_end IS NULL OR item.date_end >=%s) + AND (item.date_start_consumption IS NULL + OR item.date_start_consumption <=%s) + AND (item.date_end_consumption IS NULL + OR item.date_end_consumption >=%s) + GROUP BY item.id + ORDER BY item.applied_on, + /* REVIEW: priotrity date sale / date consumption */ + item.date_end - item.date_start ASC, + item.date_end_consumption - item.date_start_consumption ASC, + NULLIF((SELECT COUNT(1) + FROM product_pricelist_item_pms_property_rel l + WHERE item.id = l.product_pricelist_item_id) + + (SELECT COUNT(1) + FROM product_pricelist_pms_property_rel c + WHERE item.pricelist_id = c.product_pricelist_id),0) + NULLS LAST, + item.id DESC; + """, + ( + self._context["property"], + self._context["property"], + prod_tmpl_ids, + prod_ids, + categ_ids, + self.id, + date, + date, + self._context["consumption_date"], + self._context["consumption_date"], + ), + ) + item_ids = [x[0] for x in self.env.cr.fetchall()] + items = self.env["product.pricelist.item"].browse(item_ids) + if board_service: + items = items.filtered( + lambda x: x.board_service_room_type_id.id + == self._context.get("board_service") + ) + else: + items = items.filtered(lambda x: not x.board_service_room_type_id.id) + else: + items = super(ProductPricelist, self)._compute_price_rule_get_items( + products_qty_partner, date, uom_id, prod_tmpl_ids, prod_ids, categ_ids + ) + return items + + @api.constrains("is_pms_available", "availability_plan_id") + def _check_is_pms_available(self): + for record in self: + if record.is_pms_available and not record.availability_plan_id: + raise ValidationError( + _( + "If the pricelist is available in the PMS, " + "you must select an availability plan" + ) + ) + + # Action methods + # Constraints and onchanges + # @api.constrains("pricelist_type", "pms_property_ids") + # def _check_pricelist_type_property_ids(self): + # for record in self: + # if record.pricelist_type == "daily" and len(record.pms_property_ids) != 1: + # raise ValidationError( + # _( + # "A daily pricelist is used as a daily Rate Plan " + # "for room types and therefore must be related with " + # "one and only one property." + # ) + # ) + + # if record.pricelist_type == "daily" and len(record.pms_property_ids) == 1: + # pms_property_id = ( + # self.env["pms.property"].search( + # [("default_pricelist_id", "=", record.id)] + # ) + # or None + # ) + # if pms_property_id and pms_property_id != record.pms_property_ids: + # raise ValidationError( + # _("Relationship mismatch.") + # + " " + # + _( + # "This pricelist is used as default in a " + # "different property." + # ) + # ) + + def open_massive_changes_wizard(self): + + if self.ensure_one(): + return { + "view_type": "form", + "view_mode": "form", + "name": "Massive changes on Pricelist: " + self.name, + "res_model": "pms.massive.changes.wizard", + "target": "new", + "type": "ir.actions.act_window", + "context": { + "pricelist_id": self.id, + }, + } diff --git a/pms/models/product_pricelist_item.py b/pms/models/product_pricelist_item.py new file mode 100644 index 0000000000..3b07817722 --- /dev/null +++ b/pms/models/product_pricelist_item.py @@ -0,0 +1,151 @@ +# Copyright 2017 Alexandre Díaz, Pablo Quesada, Darío Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import api, fields, models + + +class ProductPricelistItem(models.Model): + _inherit = "product.pricelist.item" + _check_pms_properties_auto = True + + pms_property_ids = fields.Many2many( + string="Properties", + help="Properties with access to the element;" + " if not set, all properties can access", + comodel_name="pms.property", + relation="product_pricelist_item_pms_property_rel", + column1="product_pricelist_item_id", + column2="pms_property_id", + ondelete="restrict", + check_pms_properties=True, + ) + date_start_consumption = fields.Date( + string="Start Date Consumption", + help="Start date to apply daily pricelist items", + ) + date_end_consumption = fields.Date( + string="End Date Consumption", + help="End date to apply daily pricelist items", + ) + board_service_room_type_id = fields.Many2one( + string="Board Service", + help="Specify a Board services on Room Types.", + comodel_name="pms.board.service.room.type", + index=True, + check_pms_properties=True, + ) + pricelist_id = fields.Many2one( + string="Pricelist", + help="Pricelist in which this item is included", + index=True, + check_pms_properties=True, + ) + product_id = fields.Many2one( + string="Product", + help="Product associated with the item", + index=True, + check_pms_properties=True, + ) + product_tmpl_id = fields.Many2one( + string="Product Template", + help="Product template associated with the item", + index=True, + check_pms_properties=True, + ) + allowed_board_service_product_ids = fields.Many2many( + string="Allowed board service products", + comodel_name="product.product", + store=True, + readonly=False, + compute="_compute_allowed_board_service_product_ids", + ) + + allowed_board_service_room_type_ids = fields.Many2many( + string="Allowed board service room types", + comodel_name="pms.board.service.room.type", + store=True, + readonly=False, + compute="_compute_allowed_board_service_room_type_ids", + ) + + @api.depends("board_service_room_type_id") + def _compute_allowed_board_service_product_ids(self): + for record in self: + domain = [] + if record.board_service_room_type_id: + domain.append( + ( + "id", + "in", + record.board_service_room_type_id.board_service_line_ids.mapped( + "product_id" + ).ids, + ) + ) + allowed_board_service_product_ids = self.env["product.product"].search( + domain + ) + record.allowed_board_service_product_ids = allowed_board_service_product_ids + + @api.depends("product_id") + def _compute_allowed_board_service_room_type_ids(self): + for record in self: + allowed_board_service_room_type_ids = [] + all_board_service_room_type_ids = self.env[ + "pms.board.service.room.type" + ].search([]) + if record.product_id: + for board_service_room_type_id in all_board_service_room_type_ids: + if ( + record.product_id + in board_service_room_type_id.board_service_line_ids.mapped( + "product_id" + ) + ): + allowed_board_service_room_type_ids.append( + board_service_room_type_id.id + ) + else: + allowed_board_service_room_type_ids = ( + all_board_service_room_type_ids.ids + ) + domain = [] + if allowed_board_service_room_type_ids: + domain.append(("id", "in", allowed_board_service_room_type_ids)) + record.allowed_board_service_room_type_ids = ( + self.env["pms.board.service.room.type"].search(domain) + if domain + else False + ) + + def write(self, vals): + # Check that the price in product room types are not + # minor that min price in room type defined + # REVIEW: By the momment only check fixed prices + if "fixed_price" in vals: + if any( + [ + item.product_id.room_type_id + and item.product_id.room_type_id.min_price + and vals["fixed_price"] < item.product_id.room_type_id.min_price + for item in self + ] + ): + raise ValueError( + """The price in product room types can't be minor + that min price in room type defined""" + ) + return super().write(vals) + + def create(self, vals): + # Check that the price in product room types are not + # minor that min price in room type defined + # REVIEW: By the momment only check fixed prices + if "fixed_price" in vals: + product_id = self.env["product.product"].browse(vals["product_id"]) + if product_id.room_type_id and product_id.room_type_id.min_price: + if vals["fixed_price"] < product_id.room_type_id.min_price: + raise ValueError( + """The price in product room types can't be minor + that min price in room type defined""" + ) + return super().create(vals) diff --git a/pms/models/product_product.py b/pms/models/product_product.py new file mode 100644 index 0000000000..e6ea00801d --- /dev/null +++ b/pms/models/product_product.py @@ -0,0 +1,178 @@ +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class ProductProduct(models.Model): + _inherit = "product.product" + + board_price = fields.Float( + string="Board Service Price", + help="Get price on board service", + digits="Product Price", + compute="_compute_board_price", + ) + + room_type_id = fields.Many2one( + string="Room Type", + comodel_name="pms.room.type", + compute="_compute_room_type_id", + ) + + @api.depends_context("consumption_date") + def _compute_product_price(self): + super(ProductProduct, self)._compute_product_price() + + def _compute_board_price(self): + pms_property_id = ( + self.env.context.get("property") + or self.env.user.get_active_property_ids()[0] + ) + for record in self: + if self._context.get("board_service"): + record.board_price = ( + self.env["pms.board.service.room.type.line"] + .search( + [ + ( + "pms_board_service_room_type_id", + "=", + self._context.get("board_service"), + ), + ("product_id", "=", record.id), + ("pms_property_id", "=", pms_property_id), + ], + limit=1, + ) + .amount + ) + else: + record.board_price = False + + def _compute_room_type_id(self): + for rec in self: + room_type = self.env["pms.room.type"].search( + [ + ("product_id", "=", rec.id), + ] + ) + if room_type: + if len(room_type) > 1: + raise ValidationError( + _("More than one room found for the same product") + ) + rec.room_type_id = room_type + else: + rec.room_type_id = False + + def price_compute(self, price_type, uom=False, currency=False, company=None): + if self._context.get("board_service"): + price_type = "board_price" + return super(ProductProduct, self).price_compute( + price_type, uom, currency, company + ) + + @api.model + def _pms_get_display_price( + self, pricelist_id, product, company_id, product_qty=1, partner_id=False + ): + pricelist = self.env["product.pricelist"].browse(pricelist_id) + partner = self.env["res.partner"].browse(partner_id) if partner_id else False + if pricelist.discount_policy == "with_discount": + return product.standard_price + final_price, rule_id = pricelist.with_context( + product._context + ).get_product_price_rule(product, product_qty or 1.0, partner) + base_price, currency_id = self.with_context( + product._context + )._pms_get_real_price_currency( + product, + rule_id, + product_qty, + product.uom_id, + pricelist.id, + company_id, + partner_id, + ) + if currency_id != pricelist.currency_id.id: + base_price = ( + self.env["res.currency"] + .browse(currency_id) + .with_context(product._context) + .compute(base_price, pricelist.currency_id) + ) + # negative discounts (= surcharge) are included in the display price + return max(base_price, final_price) + + @api.model + def _pms_get_real_price_currency( + self, + product, + rule_id, + qty, + uom, + pricelist_id, + company_id=False, + partner_id=False, + ): + """Retrieve the price before applying the pricelist + :param obj product: object of current product record + :parem float qty: total quantity of product + :param tuple price_and_rule: tuple(price, suitable_rule) + coming from pricelist computation + :param obj uom: unit of measure of current order line + :param integer pricelist_id: pricelist id of sales order""" + PricelistItem = self.env["product.pricelist.item"] + field_name = "lst_price" + currency_id = None + product_currency = product.currency_id + company = self.env["res.company"].browse(company_id) if company_id else False + partner = self.env["res.partner"].browse(partner_id) if partner_id else False + if rule_id: + pricelist_item = PricelistItem.browse(rule_id) + if pricelist_item.pricelist_id.discount_policy == "without_discount": + while ( + pricelist_item.base == "pricelist" + and pricelist_item.base_pricelist_id + and pricelist_item.base_pricelist_id.discount_policy + == "without_discount" + ): + price, rule_id = pricelist_item.base_pricelist_id.with_context( + uom=uom.id + ).get_product_price_rule(product, qty, partner) + pricelist_item = PricelistItem.browse(rule_id) + + if pricelist_item.base == "standard_price": + field_name = "standard_price" + product_currency = product.cost_currency_id + elif ( + pricelist_item.base == "pricelist" and pricelist_item.base_pricelist_id + ): + field_name = "price" + product = product.with_context( + pricelist=pricelist_item.base_pricelist_id.id + ) + product_currency = pricelist_item.base_pricelist_id.currency_id + currency_id = pricelist_item.pricelist_id.currency_id + + if not currency_id: + currency_id = product_currency + cur_factor = 1.0 + else: + if currency_id.id == product_currency.id: + cur_factor = 1.0 + else: + cur_factor = currency_id._get_conversion_rate( + product_currency, + currency_id, + company, + fields.Date.today(), + ) + + product_uom = self.env.context.get("uom") or product.uom_id.id + if uom and uom.id != product_uom: + # the unit price is in a different uom + uom_factor = uom._compute_price(1.0, product.uom_id) + else: + uom_factor = 1.0 + + return product[field_name] * uom_factor * cur_factor, currency_id diff --git a/pms/models/product_template.py b/pms/models/product_template.py new file mode 100644 index 0000000000..6af72dcc39 --- /dev/null +++ b/pms/models/product_template.py @@ -0,0 +1,132 @@ +# Copyright 2017 Alexandre Díaz +# Copyright 2017 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import api, fields, models + + +class ProductTemplate(models.Model): + _inherit = "product.template" + _check_pms_properties_auto = True + + pms_property_ids = fields.Many2many( + string="Properties", + help="Properties with access to the element;" + " if not set, all properties can access", + required=False, + comodel_name="pms.property", + relation="product_template_pms_property_rel", + column1="product_tmpl_id", + column2="pms_property_id", + ondelete="restrict", + check_pms_properties=True, + ) + per_day = fields.Boolean( + string="Unit increment per day", + help="Indicates that the product is sold by days", + ) + per_person = fields.Boolean( + string="Unit increment per person", + help="Indicates that the product is sold per person", + ) + consumed_on = fields.Selection( + string="Consumed", + help="Indicates when the product is consumed", + selection=[("before", "Before night"), ("after", "After night")], + default="before", + ) + daily_limit = fields.Integer( + string="Daily limit", + help="Indicates how much products can consumed in one day", + compute="_compute_daily_limit", + inverse="_inverse_daily_limit", + ) + list_price = fields.Float( + compute="_compute_list_price", + inverse="_inverse_list_price", + ) + is_extra_bed = fields.Boolean( + string="Is extra bed", + help="Indicates if that product is a extra bed, add +1 capacity in the room", + default=False, + ) + is_crib = fields.Boolean( + string="Is a baby crib", + help="Indicates if that product is a crib", + default=False, + ) + is_pms_available = fields.Boolean( + string="Is available in PMS", + help="Indicates if that product is available in PMS", + default=True, + ) + + @api.depends_context("allowed_pms_property_ids") + def _compute_daily_limit(self): + for record in self: + pms_property_id = ( + self.env.context.get("property") + or self.env.user.get_active_property_ids()[0] + ) + record.daily_limit = self.env["ir.pms.property"].get_field_value( + pms_property_id, + self._name, + "daily_limit", + record.id, + type(record.daily_limit), + ) + + @api.depends_context("allowed_pms_property_ids") + def _compute_list_price(self): + for record in self: + pms_property_id = ( + self.env.context.get("property") + or self.env.user.get_active_property_ids()[0] + ) + record.list_price = self.env["ir.pms.property"].get_field_value( + pms_property_id, + self._name, + "list_price", + record.id, + type(record.list_price), + ) + + def _inverse_daily_limit(self): + for record in self: + pms_property_id = ( + self.env.context.get("property") + or self.env.user.get_active_property_ids()[0] + ) + self.env["ir.pms.property"].set_field_value( + pms_property_id, + self._name, + "daily_limit", + record.id, + record.daily_limit, + ) + + def _inverse_list_price(self): + for record in self: + pms_property_id = ( + self.env.context.get("property") + or self.env.user.get_active_property_ids()[0] + ) + self.env["ir.pms.property"].set_field_value( + pms_property_id, self._name, "list_price", record.id, record.list_price + ) + # Set default value in other properties + other_properties = self.env["pms.property"].search([]) + for other_property in other_properties.ids: + if not self.env["ir.pms.property"].get_field_value( + other_property, + self._name, + "list_price", + record.id, + type(record.list_price), + ): + self.env["ir.pms.property"].set_field_value( + other_property, + self._name, + "list_price", + record.id, + record.list_price, + ) diff --git a/pms/models/res_company.py b/pms/models/res_company.py new file mode 100644 index 0000000000..cb3f5d89eb --- /dev/null +++ b/pms/models/res_company.py @@ -0,0 +1,60 @@ +# 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 + + +class ResCompany(models.Model): + _inherit = "res.company" + + pms_property_ids = fields.One2many( + string="Properties", + help="Properties with access to the element", + comodel_name="pms.property", + inverse_name="company_id", + ) + + url_advert = fields.Char(string="Url Advert", help="Url to identify the ad") + + privacy_policy = fields.Html( + string="Privacy Policy", + help="Authorization by the user for the" "manage of their personal data", + ) + + check_min_partner_data_invoice = fields.Boolean( + string="Check minimum partner data for invoices", + help="""Check minimum partner data for invoices: + - VAT, name, street, city, country""", + default=False, + ) + + pms_invoice_downpayment_policy = fields.Selection( + selection=[ + ("no", "Manual"), + ("all", "All"), + ("checkout_past_month", "Checkout past month"), + ], + string="Downpayment policy invoce", + help=""" + - Manual: Downpayment invoice will be created manually + - All: Downpayment invoice will be created automatically + - Current Month: Downpayment invoice will be created automatically + only for reservations with checkout date past of current month + """, + default="no", + ) + + document_partner_required = fields.Boolean( + string="Document partner required", + help="""If true, the partner document is required + to create a new contact""", + default=False, + ) + + cancel_penalty_product_id = fields.Many2one( + string="Cancel penalty product", + help="Product used to calculate the cancel penalty", + comodel_name="product.product", + index=True, + ondelete="restrict", + ) diff --git a/pms/models/res_country.py b/pms/models/res_country.py new file mode 100644 index 0000000000..953355a97b --- /dev/null +++ b/pms/models/res_country.py @@ -0,0 +1,9 @@ +from odoo import fields, models + + +class Country(models.Model): + _inherit = "res.country" + _description = "Country" + _order = "priority, name" + + priority = fields.Integer(string="Priority", default=1000) diff --git a/pms/models/res_partner.py b/pms/models/res_partner.py new file mode 100644 index 0000000000..3863bff41b --- /dev/null +++ b/pms/models/res_partner.py @@ -0,0 +1,799 @@ +# Copyright 2017 Alexandre Díaz +# Copyright 2017 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + +_logger = logging.getLogger(__name__) + + +class ResPartner(models.Model): + _inherit = "res.partner" + + reservations_count = fields.Integer( + string="Number of Reservations", + help="Number of reservations of the partner", + compute="_compute_reservations_count", + ) + folios_count = fields.Integer( + string="Number of Folios", + help="Number of folios of the partner", + compute="_compute_folios_count", + ) + is_agency = fields.Boolean( + string="Is Agency", help="Indicates if the partner is an agency" + ) + sale_channel_id = fields.Many2one( + string="Sale Channel", + help="The sale channel of the partner", + comodel_name="pms.sale.channel", + domain=[("channel_type", "=", "indirect")], + ondelete="restrict", + index=True, + ) + default_commission = fields.Integer(string="Commission", help="Default commission") + apply_pricelist = fields.Boolean( + string="Apply Pricelist", + help="Indicates if agency pricelist is applied to his reservations", + ) + invoice_to_agency = fields.Selection( + string="Invoice Agency", + help="Indicates if agency invoices partner", + selection=[ + ("never", "Never"), + ("manual", "Manual"), + ("always", "Always"), + ], + default="never", + required=True, + ) + pms_property_ids = fields.Many2many( + string="Properties", + help="Properties with access to the element" + " if not set, all properties can access", + required=False, + comodel_name="pms.property", + relation="res_partner_pms_property_rel", + column1="res_partner_id", + column2="pms_property_id", + ondelete="restrict", + check_pms_properties=True, + ) + pms_checkin_partner_ids = fields.One2many( + string="Checkin Partners", + help="Associated checkin partners", + comodel_name="pms.checkin.partner", + inverse_name="partner_id", + ) + pms_reservation_ids = fields.One2many( + string="Reservations", + help="Associated reservation", + comodel_name="pms.reservation", + inverse_name="partner_id", + ) + pms_folio_ids = fields.One2many( + string="Folios", + help="Associated Folios", + comodel_name="pms.folio", + inverse_name="partner_id", + ) + gender = fields.Selection( + readonly=False, + store=True, + compute="_compute_gender", + ) + birthdate_date = fields.Date( + readonly=False, + store=True, + compute="_compute_birthdate_date", + ) + nationality_id = fields.Many2one( + readonly=False, + store=True, + index=True, + compute="_compute_nationality_id", + ) + email = fields.Char( + readonly=False, + store=True, + compute="_compute_email", + ) + mobile = fields.Char( + readonly=False, + store=True, + compute="_compute_mobile", + ) + phone = fields.Char( + readonly=False, + store=True, + compute="_compute_phone", + ) + firstname = fields.Char( + readonly=False, + store=True, + compute="_compute_firstname", + ) + + lastname = fields.Char( + readonly=False, + store=True, + compute="_compute_lastname", + ) + lastname2 = fields.Char( + readonly=False, + store=True, + compute="_compute_lastname2", + ) + country_id = fields.Many2one( + readonly=False, + store=True, + index=True, + compute="_compute_country_id", + ) + state_id = fields.Many2one( + readonly=False, + store=True, + index=True, + compute="_compute_state_id", + ) + city = fields.Char( + readonly=False, + store=True, + compute="_compute_city", + ) + street = fields.Char( + readonly=False, + store=True, + compute="_compute_street", + ) + street2 = fields.Char( + readonly=False, + store=True, + compute="_compute_street2", + ) + zip = fields.Char( + readonly=False, + store=True, + compute="_compute_zip", + ) + comment = fields.Text( + tracking=True, + ) + reservation_possible_customer_id = fields.Many2one( + string="Possible Customer In Reservation", comodel_name="pms.reservation" + ) + folio_possible_customer_id = fields.Many2one( + string="Possible Customer In Folio", comodel_name="pms.folio" + ) + checkin_partner_possible_customer_id = fields.Many2one( + string="Possible Customer In Checkin Partner", + comodel_name="pms.checkin.partner", + ) + invoicing_policy = fields.Selection( + string="Invoicing Policy", + help="""The invoicing policy of the partner, + set Property to user the policy configured in the Property""", + selection=[ + ("property", "Property Policy Invoice"), + ("manual", "Manual"), + ("checkout", "From Checkout"), + ("month_day", "Month Day Invoice"), + ], + default="property", + ) + invoicing_month_day = fields.Integer( + string="Invoicing Month Day", + help="The day of the month to invoice", + ) + margin_days_autoinvoice = fields.Integer( + string="Days from Checkout", + help="Days from Checkout to generate the invoice", + ) + residence_street = fields.Char( + string="Street of residence", + help="Street of the guest's residence", + readonly=False, + store=True, + compute="_compute_residence_street", + ) + residence_street2 = fields.Char( + string="Second street of residence", + help="Second street of the guest's residence", + readonly=False, + store=True, + compute="_compute_residence_street2", + ) + residence_zip = fields.Char( + string="Zip of residence", + help="Zip of the guest's residence", + readonly=False, + store=True, + compute="_compute_residence_zip", + change_default=True, + ) + residence_city = fields.Char( + string="city of residence", + help="City of the guest's residence", + readonly=False, + store=True, + compute="_compute_residence_city", + ) + residence_country_id = fields.Many2one( + string="Country of residence", + help="Partner country of residence", + readonly=False, + store=True, + index=True, + compute="_compute_residence_country_id", + comodel_name="res.country", + ) + residence_state_id = fields.Many2one( + string="State of residence", + help="Partner state of residence", + readonly=False, + store=True, + index=True, + compute="_compute_residence_state_id", + comodel_name="res.country.state", + ) + + @api.depends("pms_checkin_partner_ids", "pms_checkin_partner_ids.gender") + def _compute_gender(self): + if hasattr(super(), "_compute_gender"): + super()._compute_gender() + for record in self: + if record.pms_checkin_partner_ids: + last_update_gender = record.pms_checkin_partner_ids.filtered( + lambda x: x.write_date + == max(record.pms_checkin_partner_ids.mapped("write_date")) + ) + if last_update_gender and last_update_gender[0].gender: + record.gender = last_update_gender[0].gender + + @api.depends("pms_checkin_partner_ids", "pms_checkin_partner_ids.birthdate_date") + def _compute_birthdate_date(self): + if hasattr(super(), "_compute_birthdate_date"): + super()._compute_birthdate_date() + for record in self: + if record.pms_checkin_partner_ids: + last_update_birthdate = record.pms_checkin_partner_ids.filtered( + lambda x: x.write_date + == max(record.pms_checkin_partner_ids.mapped("write_date")) + ) + if last_update_birthdate and last_update_birthdate[0].birthdate_date: + record.birthdate_date = last_update_birthdate[0].birthdate_date + + @api.depends("pms_checkin_partner_ids", "pms_checkin_partner_ids.nationality_id") + def _compute_nationality_id(self): + if hasattr(super(), "_compute_nationality_id"): + super()._compute_nationality_id() + for record in self: + if record.pms_checkin_partner_ids: + last_update_nationality = record.pms_checkin_partner_ids.filtered( + lambda x: x.write_date + == max(record.pms_checkin_partner_ids.mapped("write_date")) + ) + if ( + last_update_nationality + and last_update_nationality[0].nationality_id + ): + record.nationality_id = last_update_nationality[0].nationality_id + if not record.nationality_id and record.country_id: + record.nationality_id = record.country_id + + @api.depends("pms_checkin_partner_ids", "pms_checkin_partner_ids.phone") + def _compute_phone(self): + if hasattr(super(), "_compute_phone"): + super()._compute_phone() + for record in self: + if record.pms_checkin_partner_ids: + last_update_phone = record.pms_checkin_partner_ids.filtered( + lambda x: x.write_date + == max(record.pms_checkin_partner_ids.mapped("write_date")) + ) + if last_update_phone and last_update_phone[0].phone: + record.phone = last_update_phone[0].phone + + @api.depends("pms_checkin_partner_ids", "pms_checkin_partner_ids.residence_street") + def _compute_residence_street(self): + if hasattr(super(), "_compute_residence_street"): + super()._compute_residence_street() + for record in self: + if record.pms_checkin_partner_ids: + last_update_street = record.pms_checkin_partner_ids.filtered( + lambda x: x.write_date + == max(record.pms_checkin_partner_ids.mapped("write_date")) + ) + if last_update_street and last_update_street[0].residence_street: + record.residence_street = last_update_street[0].residence_street + + @api.depends("pms_checkin_partner_ids", "pms_checkin_partner_ids.residence_street2") + def _compute_residence_street2(self): + if hasattr(super(), "_compute_residence_street2"): + super()._compute_residence_street2() + for record in self: + if record.pms_checkin_partner_ids: + last_update_street2 = record.pms_checkin_partner_ids.filtered( + lambda x: x.write_date + == max(record.pms_checkin_partner_ids.mapped("write_date")) + ) + if last_update_street2 and last_update_street2[0].residence_street2: + record.residence_street2 = last_update_street2[0].residence_street2 + + @api.depends("pms_checkin_partner_ids", "pms_checkin_partner_ids.residence_zip") + def _compute_residence_zip(self): + if hasattr(super(), "_compute_residence_zip"): + super()._compute_residence_zip() + for record in self: + if record.pms_checkin_partner_ids: + last_update_zip = record.pms_checkin_partner_ids.filtered( + lambda x: x.write_date + == max(record.pms_checkin_partner_ids.mapped("write_date")) + ) + if last_update_zip and last_update_zip[0].residence_zip: + record.residence_zip = last_update_zip[0].residence_zip + + @api.depends("pms_checkin_partner_ids", "pms_checkin_partner_ids.residence_city") + def _compute_residence_city(self): + if hasattr(super(), "_compute_residence_city"): + super()._compute_residence_city() + for record in self: + if record.pms_checkin_partner_ids: + last_update_city = record.pms_checkin_partner_ids.filtered( + lambda x: x.write_date + == max(record.pms_checkin_partner_ids.mapped("write_date")) + ) + if last_update_city and last_update_city[0].residence_city: + record.residence_city = last_update_city[0].residence_city + + @api.depends( + "pms_checkin_partner_ids", + "pms_checkin_partner_ids.residence_country_id", + "nationality_id", + ) + def _compute_residence_country_id(self): + if hasattr(super(), "_compute_residence_country_id"): + super()._compute_residence_country_id() + for record in self: + if record.pms_checkin_partner_ids: + last_update_country = record.pms_checkin_partner_ids.filtered( + lambda x: x.write_date + == max(record.pms_checkin_partner_ids.mapped("write_date")) + ) + if last_update_country and last_update_country[0].residence_country_id: + record.residence_country_id = last_update_country[ + 0 + ].residence_country_id + + @api.depends( + "pms_checkin_partner_ids", "pms_checkin_partner_ids.residence_state_id" + ) + def _compute_residence_state_id(self): + if hasattr(super(), "_compute_residence_state_id"): + super()._compute_residence_state_id() + for record in self: + if record.pms_checkin_partner_ids: + last_update_state = record.pms_checkin_partner_ids.filtered( + lambda x: x.write_date + == max(record.pms_checkin_partner_ids.mapped("write_date")) + ) + if last_update_state and last_update_state[0].residence_state_id: + record.residence_state_id = last_update_state[0].residence_state_id + + @api.depends( + "pms_checkin_partner_ids", + "pms_checkin_partner_ids.email", + "pms_reservation_ids", + "pms_reservation_ids.email", + "pms_folio_ids", + "pms_folio_ids.email", + ) + def _compute_email(self): + if hasattr(super(), "_compute_email"): + super()._compute_email() + for record in self: + if record.pms_checkin_partner_ids: + last_update_checkin_mail = record.pms_checkin_partner_ids.filtered( + lambda x: x.write_date + == max(record.pms_checkin_partner_ids.mapped("write_date")) + ) + if last_update_checkin_mail and last_update_checkin_mail[0].email: + record.email = last_update_checkin_mail[0].email + + @api.depends( + "pms_checkin_partner_ids", + "pms_checkin_partner_ids.mobile", + "pms_reservation_ids", + "pms_reservation_ids.mobile", + "pms_folio_ids", + "pms_folio_ids.mobile", + ) + def _compute_mobile(self): + if hasattr(super(), "_compute_mobile"): + super()._compute_mobile() + for record in self: + if record.pms_checkin_partner_ids: + last_update_mobile = record.pms_checkin_partner_ids.filtered( + lambda x: x.write_date + == max(record.pms_checkin_partner_ids.mapped("write_date")) + ) + if last_update_mobile and last_update_mobile[0].mobile: + record.mobile = last_update_mobile[0].mobile + + @api.depends("pms_checkin_partner_ids", "pms_checkin_partner_ids.firstname") + def _compute_firstname(self): + if hasattr(super(), "_compute_firstname"): + super()._compute_firstname() + for record in self: + if record.pms_checkin_partner_ids: + last_update_firstname = record.pms_checkin_partner_ids.filtered( + lambda x: x.write_date + == max(record.pms_checkin_partner_ids.mapped("write_date")) + ) + if last_update_firstname and last_update_firstname[0].firstname: + record.firstname = last_update_firstname[0].firstname + + @api.depends("pms_checkin_partner_ids", "pms_checkin_partner_ids.lastname") + def _compute_lastname(self): + if hasattr(super(), "_compute_lastname"): + super()._compute_lastname() + for record in self: + if record.pms_checkin_partner_ids: + last_update_lastname = record.pms_checkin_partner_ids.filtered( + lambda x: x.write_date + == max(record.pms_checkin_partner_ids.mapped("write_date")) + ) + if last_update_lastname and last_update_lastname[0].lastname: + record.lastname = last_update_lastname[0].lastname + + @api.depends("pms_checkin_partner_ids", "pms_checkin_partner_ids.lastname2") + def _compute_lastname2(self): + if hasattr(super(), "_compute_lastname2"): + super()._compute_lastname2() + for record in self: + if record.pms_checkin_partner_ids: + last_update_lastname2 = record.pms_checkin_partner_ids.filtered( + lambda x: x.write_date + == max(record.pms_checkin_partner_ids.mapped("write_date")) + ) + if last_update_lastname2 and last_update_lastname2[0].lastname2: + record.lastname2 = last_update_lastname2[0].lastname2 + + @api.depends("id_numbers") + def _compute_country_id(self): + if hasattr(super(), "_compute_country_id"): + super()._compute_country_id() + for record in self: + if ( + not record.parent_id + and not record.country_id + and record.id_numbers + and record.id_numbers.country_id + ): + record.country_id = record.id_numbers[0].country_id + + @api.depends("residence_state_id") + def _compute_state_id(self): + if hasattr(super(), "_compute_state_id"): + super()._compute_state_id() + for record in self: + if ( + not record.parent_id + and not record.state_id + and record.residence_state_id + ): + record.state_id = record.residence_state_id + + @api.depends("residence_city") + def _compute_city(self): + if hasattr(super(), "_compute_city"): + super()._compute_city() + for record in self: + if not record.parent_id and not record.city and record.residence_city: + record.city = record.residence_city + + @api.depends("residence_street") + def _compute_street(self): + if hasattr(super(), "_compute_street"): + super()._compute_street() + for record in self: + if not record.parent_id and not record.street and record.residence_street: + record.street = record.residence_street + + @api.depends("residence_street2") + def _compute_street2(self): + if hasattr(super(), "_compute_street2"): + super()._compute_street2() + for record in self: + if not record.parent_id and not record.street2 and record.residence_street2: + record.street2 = record.residence_street2 + + @api.depends("residence_zip") + def _compute_zip(self): + if hasattr(super(), "_compute_zip"): + super()._compute_zip() + for record in self: + if not record.parent_id and not record.zip and record.residence_zip: + record.zip = record.residence_zip + + def _compute_reservations_count(self): + # Return reservation with partner included in reservation and/or checkin + pms_reservation_obj = self.env["pms.reservation"] + for record in self: + checkin_reservation_ids = ( + self.env["pms.checkin.partner"] + .search([("partner_id", "=", record.id)]) + .mapped("reservation_id.id") + ) + record.reservations_count = pms_reservation_obj.search_count( + [ + "|", + ( + "partner_id.id", + "child_of", + record.id if isinstance(record.id, int) else False, + ), + ("id", "in", checkin_reservation_ids), + ] + ) + + def action_partner_reservations(self): + self.ensure_one() + checkin_reservation_ids = ( + self.env["pms.checkin.partner"] + .search([("partner_id", "=", self.id)]) + .mapped("reservation_id.id") + ) + reservations = self.env["pms.reservation"].search( + [ + "|", + ( + "partner_id.id", + "child_of", + self.id if isinstance(self.id, int) else False, + ), + ("id", "in", checkin_reservation_ids), + ] + ) + action = self.env["ir.actions.actions"]._for_xml_id( + "pms.open_pms_reservation_form_tree_all" + ) + if len(reservations) > 1: + action["domain"] = [("id", "in", reservations.ids)] + elif len(reservations) == 1: + form_view = [(self.env.ref("pms.pms_reservation_view_form").id, "form")] + if "views" in action: + action["views"] = form_view + [ + (state, view) for state, view in action["views"] if view != "form" + ] + else: + action["views"] = form_view + action["res_id"] = reservations.id + else: + action = {"type": "ir.actions.act_window_close"} + + if len(self) == 1: + context = { + "default_partner_id": self.id, + "default_user_id": self.user_id.id, + } + action["context"] = context + return action + + def _compute_folios_count(self): + # Return folios count with partner included in folio and/or folio checkins + pms_folio_obj = self.env["pms.folio"] + for record in self: + checkin_folio_ids = ( + self.env["pms.checkin.partner"] + .search([("partner_id", "=", record.id)]) + .mapped("folio_id.id") + ) + record.folios_count = pms_folio_obj.search_count( + [ + "|", + ( + "partner_id.id", + "=", + record.id if isinstance(record.id, int) else False, + ), + ("id", "in", checkin_folio_ids), + ] + ) + + def action_partner_folios(self): + self.ensure_one() + checkin_folio_ids = ( + self.env["pms.checkin.partner"] + .search([("partner_id", "=", self.id)]) + .mapped("folio_id.id") + ) + folios = self.env["pms.folio"].search( + [ + "|", + ( + "partner_id.id", + "child_of", + self.id if isinstance(self.id, int) else False, + ), + ("id", "in", checkin_folio_ids), + ] + ) + action = self.env["ir.actions.actions"]._for_xml_id( + "pms.open_pms_folio1_form_tree_all" + ) + if len(folios) > 1: + action["domain"] = [("id", "in", folios.ids)] + elif len(folios) == 1: + form_view = [(self.env.ref("pms.pms_folio_view_form").id, "form")] + if "views" in action: + action["views"] = form_view + [ + (state, view) for state, view in action["views"] if view != "form" + ] + else: + action["views"] = form_view + action["res_id"] = folios.id + else: + action = {"type": "ir.actions.act_window_close"} + + if len(self) == 1: + context = { + "default_partner_id": self.id, + "default_user_id": self.user_id.id, + } + action["context"] = context + return action + + @api.constrains("is_agency", "sale_channel_id") + def _check_is_agency(self): + for record in self: + if record.is_agency and not record.sale_channel_id: + raise models.ValidationError(_("Sale Channel must be entered")) + if record.is_agency and record.sale_channel_id.channel_type != "indirect": + raise models.ValidationError( + _("Sale Channel for an agency must be indirect") + ) + if not record.is_agency and record.sale_channel_id: + record.sale_channel_id = None + + # REVIEW: problems with odoo demo data + # @api.constrains("mobile", "email") + # def _check_duplicated(self): + # for record in self: + # partner, field = record._search_duplicated() + # if partner: + # raise models.ValidationError( + # _( + # "Partner %s found with same %s (%s)", + # partner.name, + # partner._fields[field].string, + # getattr(record, field), + # ) + # ) + + def _search_duplicated(self): + self.ensure_one() + partner = False + for field in self._get_key_fields(): + if getattr(self, field): + partner = self.search( + [(field, "=", getattr(self, field)), ("id", "!=", self.id)] + ) + if partner: + field = field + return partner, field + + @api.model + def _get_key_fields(self): + key_fields = super(ResPartner, self)._get_key_fields() + key_fields.extend(["document_number"]) + return key_fields + + def _check_enought_invoice_data(self): + self.ensure_one() + # Template to be inherited by localization modules + return True + + def unlink(self): + dummy, various_partner_id = self.env["ir.model.data"].get_object_reference( + "pms", "various_pms_partner" + ) + if various_partner_id in self.ids: + various_partner = self.browse(various_partner_id) + raise ValidationError( + _("The partner %s cannot be deleted"), various_partner.name + ) + return super().unlink() + + @api.model + def create(self, values): + check_missing_document = self._check_document_partner_required(values) + if check_missing_document: + raise ValidationError(_("A document identification is required")) + + return super(ResPartner, self).create(values) + + def write(self, vals): + check_missing_document = self._check_document_partner_required( + vals, partners=self + ) + if check_missing_document: + # REVIEW: Deactivate this check for now, because it can generate problems + # with other modules that update technical partner fields + _logger.warning( + _("Partner without document identification, update vals %s"), vals + ) + # We only check if the vat or document_number is updated + if "vat" in vals or "document_number" in vals: + raise ValidationError(_("A document identification is required")) + return super().write(vals) + + @api.model + def _check_document_partner_required(self, vals, partners=False): + company_ids = ( + self.env["res.company"].sudo().search([]).ids + if (not partners or any([not partner.company_id for partner in partners])) + else partners.mapped("company_id.id") + ) + if not self.env.context.get("avoid_document_restriction") and any( + [ + self.env["res.company"] + .sudo() + .browse(company_id) + .document_partner_required + for company_id in company_ids + ] + ): + return self._missing_document(vals, partners) + return False + + @api.model + def _missing_document(self, vals, partners=False): + # If not is a partner contact and not have vat, then return missing document True + if ( + not vals.get("parent_id") + or (partners and any([not partner.parent_id for partner in partners])) + ) and ( + vals.get("vat") is False + or vals.get("vat") == "" + or ( + "vat" not in vals + and ( + any([not partner.vat for partner in partners]) if partners else True + ) + ) + or vals.get("country_id") is False + or vals.get("country_id") == "" + or ( + "country_id" not in vals + and ( + any([not partner.country_id for partner in partners]) + if partners + else True + ) + ) + ): + return True + return False + + @api.constrains("is_agency", "property_product_pricelist") + def _check_agency_pricelist(self): + if any( + record.is_agency and not record.property_product_pricelist.is_pms_available + for record in self + ): + raise models.ValidationError( + _( + """ + Agency must have a PMS pricelist, please review the + pricelists configuration (%s) to allow it for PMS, + or the pricelist selected for the agencies: %s + """ + ) + % ( + ",".join(self.mapped("property_product_pricelist.name")), + "".join(self.mapped("name")), + ) + ) diff --git a/pms/models/res_partner_category.py b/pms/models/res_partner_category.py new file mode 100644 index 0000000000..2ea27075cf --- /dev/null +++ b/pms/models/res_partner_category.py @@ -0,0 +1,7 @@ +from odoo import fields, models + + +class ResPartnerCategory(models.Model): + _inherit = "res.partner.category" + + is_used_in_checkin = fields.Boolean(string="Used in checkin") diff --git a/pms/models/res_partner_id_category.py b/pms/models/res_partner_id_category.py new file mode 100644 index 0000000000..bffa574154 --- /dev/null +++ b/pms/models/res_partner_id_category.py @@ -0,0 +1,15 @@ +from odoo import fields, models + + +class ResPartnerIdCategory(models.Model): + _inherit = "res.partner.id_category" + + country_ids = fields.Many2many( + comodel_name="res.country", + string="Countries", + ) + + priority = fields.Integer( + string="Priority", + default=100, + ) diff --git a/pms/models/res_partner_id_number.py b/pms/models/res_partner_id_number.py new file mode 100644 index 0000000000..f2f6b183e0 --- /dev/null +++ b/pms/models/res_partner_id_number.py @@ -0,0 +1,146 @@ +# Copyright 2004-2010 Tiny SPRL http://tiny.be +# Copyright 2010-2012 ChriCar Beteiligungs- und Beratungs- GmbH +# http://www.camptocamp.at +# Copyright 2015 Antiun Ingenieria, SL (Madrid, Spain) +# http://www.antiun.com +# Antonio Espinosa +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class ResPartnerIdNumber(models.Model): + _inherit = "res.partner.id_number" + + name = fields.Char( + readonly=False, + store=True, + compute="_compute_name", + ) + + category_id = fields.Many2one( + readonly=False, + store=True, + compute="_compute_category_id", + ) + + valid_from = fields.Date( + readonly=False, + store=True, + compute="_compute_valid_from", + ) + + country_id = fields.Many2one( + string="Country", + comodel_name="res.country", + help="Country of the document", + compute="_compute_country_id", + store=True, + readonly=False, + ) + + @api.depends("partner_id", "partner_id.pms_checkin_partner_ids.document_number") + def _compute_name(self): + if hasattr(super(), "_compute_name"): + super()._compute_name() + for record in self: + if record.partner_id.pms_checkin_partner_ids: + last_update_name = record.partner_id.pms_checkin_partner_ids.filtered( + lambda x: x.document_id == record + and x.write_date + == max( + record.partner_id.pms_checkin_partner_ids.mapped("write_date") + ) + ) + if last_update_name and last_update_name[0].document_number: + record.name = last_update_name[0].document_number + + @api.depends( + "partner_id", "partner_id.pms_checkin_partner_ids.document_expedition_date" + ) + def _compute_valid_from(self): + if hasattr(super(), "_compute_valid_from"): + super()._compute_valid_from() + for record in self: + if record.partner_id.pms_checkin_partner_ids: + last_update_valid_from = ( + record.partner_id.pms_checkin_partner_ids.filtered( + lambda x: x.document_id == record + and x.write_date + == max( + record.partner_id.pms_checkin_partner_ids.mapped( + "write_date" + ) + ) + ) + ) + if ( + last_update_valid_from + and last_update_valid_from[0].document_expedition_date + ): + record.valid_from = last_update_valid_from[ + 0 + ].document_expedition_date + + @api.depends("partner_id", "partner_id.pms_checkin_partner_ids.document_type") + def _compute_category_id(self): + if hasattr(super(), "_compute_category_id"): + super()._compute_category_id() + for record in self: + if record.partner_id.pms_checkin_partner_ids: + last_update_category_id = ( + record.partner_id.pms_checkin_partner_ids.filtered( + lambda x: x.document_id == record + and x.write_date + == max( + record.partner_id.pms_checkin_partner_ids.mapped( + "write_date" + ) + ) + ) + ) + if last_update_category_id and last_update_category_id[0].document_type: + record.category_id = last_update_category_id[0].document_type + + @api.depends("partner_id", "partner_id.pms_checkin_partner_ids.document_country_id") + def _compute_country_id(self): + for record in self: + if record.partner_id.pms_checkin_partner_ids: + last_update_document = ( + record.partner_id.pms_checkin_partner_ids.filtered( + lambda x: x.document_id == record + and x.write_date + == max( + record.partner_id.pms_checkin_partner_ids.mapped( + "write_date" + ) + ) + ) + ) + if last_update_document and last_update_document[0].document_country_id: + record.country_id = last_update_document[0].document_country_id + + @api.constrains("partner_id", "category_id") + def _check_category_id_unique(self): + for record in self: + id_number = self.env["res.partner.id_number"].search( + [ + ("partner_id", "=", record.partner_id.id), + ("category_id", "=", record.category_id.id), + ] + ) + if len(id_number) > 1: + raise ValidationError(_("Partner already has this document type")) + + @api.constrains("country_id", "category_id") + def _check_document_country_id_category_id_consistence(self): + for record in self: + if record.category_id and record.country_id: + if ( + record.category_id.country_ids + and record.country_id not in record.category_id.country_ids + ): + raise ValidationError( + _("Country is not allowed for this document type") + ) diff --git a/pms/models/res_users.py b/pms/models/res_users.py new file mode 100644 index 0000000000..b68cd6a572 --- /dev/null +++ b/pms/models/res_users.py @@ -0,0 +1,78 @@ +# Copyright 2019 Pablo Quesada +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError +from odoo.http import request + + +class ResUsers(models.Model): + _inherit = "res.users" + + pms_property_id = fields.Many2one( + string="Default Property", + help="The property that is selected within " "those allowed for the user", + comodel_name="pms.property", + domain="[('id','in',pms_property_ids)]", + context={"user_preference": True}, + index=True, + ) + pms_property_ids = fields.Many2many( + string="Properties", + help="The properties allowed for this user", + comodel_name="pms.property", + relation="pms_property_users_rel", + column1="user_id", + column2="pms_property_id", + domain="[('company_id','in',company_ids)]", + ) + + @api.model + def get_active_property_ids(self): + # TODO: Require performance test and security (dont allow any property id) + # checks (Review lazy_property decorator?) + user_property_ids = self.env.user.pms_property_ids.ids + if request and request.httprequest.cookies.get("pms_pids"): + active_property_ids = list( + map(int, request.httprequest.cookies.get("pms_pids", "").split(",")) + ) + active_property_ids = [ + pid for pid in active_property_ids if pid in user_property_ids + ] + return self.env["pms.property"].browse(active_property_ids).ids + return user_property_ids + + def _is_property_member(self, pms_property_id): + self.ensure_one() + # TODO: Use pms_teams and roles to check if user is member of property + # and analice the management of external users like a Call Center + return self.env.user.has_group( + "pms.group_pms_user" + ) and not self.env.user.has_group("pms.group_pms_call") + + @api.constrains("pms_property_id", "pms_property_ids") + def _check_property_in_allowed_properties(self): + if any(user.pms_property_id not in user.pms_property_ids for user in self): + raise ValidationError( + _("The chosen property is not in the allowed properties for this user") + ) + + @api.constrains("pms_property_ids", "company_id") + def _check_company_in_property_ids(self): + for record in self: + for pms_property in record.pms_property_ids: + if pms_property.company_id not in record.company_ids: + raise ValidationError( + _("Some properties do not belong to the allowed companies") + ) + + # Inherit Create and Write method to set context avoid_document_restriction + @api.model + def create(self, vals): + return super( + ResUsers, self.with_context(avoid_document_restriction=True) + ).create(vals) + + def write(self, vals): + return super( + ResUsers, self.with_context(avoid_document_restriction=True) + ).write(vals) diff --git a/pms/readme/CONFIGURE.rst b/pms/readme/CONFIGURE.rst new file mode 100644 index 0000000000..940574e837 --- /dev/null +++ b/pms/readme/CONFIGURE.rst @@ -0,0 +1,3 @@ +You will find the hotel settings in PMS Management > Configuration > Properties > Your Property. + +This module required additional configuration for company, accounting, invoicing and user privileges. diff --git a/pms/readme/CONTRIBUTORS.rst b/pms/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..e0a785d42b --- /dev/null +++ b/pms/readme/CONTRIBUTORS.rst @@ -0,0 +1,11 @@ +* Alexandre Díaz +* Pablo Quesada +* Jose Luis Algara +* `Commit [Sun] `: + + * Dario Lodeiros + * Eric Antones + * Sara Lago + * Brais Abeijon + * Miguel Padin +* Omar Castiñeira diff --git a/pms/readme/DESCRIPTION.rst b/pms/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..9001d9117a --- /dev/null +++ b/pms/readme/DESCRIPTION.rst @@ -0,0 +1,5 @@ +This module is an all-in-one property management system (PMS) focused on medium-sized properties +for managing every aspect of your property's daily operations. + +You can manage properties with multi-property and multi-company support, including your rooms inventory, +reservations, check-in, daily reports, board services, rate and availability plans among other property functionalities. diff --git a/pms/readme/INSTALL.rst b/pms/readme/INSTALL.rst new file mode 100644 index 0000000000..1661ad5e73 --- /dev/null +++ b/pms/readme/INSTALL.rst @@ -0,0 +1,2 @@ +This module depends on modules ``base``, ``mail``, ``sale`` and ``multi_pms_properties``. +Ensure yourself to have all them in your addons list. diff --git a/pms/readme/USAGE.rst b/pms/readme/USAGE.rst new file mode 100644 index 0000000000..e6a897c6ad --- /dev/null +++ b/pms/readme/USAGE.rst @@ -0,0 +1 @@ +To use this module, please, read the complete user guide at ``_. diff --git a/pms/report/invoice.xml b/pms/report/invoice.xml new file mode 100644 index 0000000000..860104a1ed --- /dev/null +++ b/pms/report/invoice.xml @@ -0,0 +1,55 @@ + + + + diff --git a/pms/report/pms_folio.xml b/pms/report/pms_folio.xml new file mode 100644 index 0000000000..ced7248688 --- /dev/null +++ b/pms/report/pms_folio.xml @@ -0,0 +1,13 @@ + + + + Report Folio + pms.folio + qweb-pdf + pms.report_folio + pms.report_folio + (object.state in ('draft', 'sent') and 'Quotation - %s' % (object.name)) or 'Order - %s' % (object.name) + + diff --git a/pms/report/pms_folio_templates.xml b/pms/report/pms_folio_templates.xml new file mode 100644 index 0000000000..7123852336 --- /dev/null +++ b/pms/report/pms_folio_templates.xml @@ -0,0 +1,363 @@ + + + + + + + + diff --git a/pms/report/proforma_report.xml b/pms/report/proforma_report.xml new file mode 100644 index 0000000000..3f678bf334 --- /dev/null +++ b/pms/report/proforma_report.xml @@ -0,0 +1,13 @@ + + + + PRO-FORMA Invoice + account.move + qweb-pdf + pms.report_pms_pro_forma + pms.report_pms_pro_forma + 'PRO-FORMA - %s' % (object.name) + + report + + diff --git a/pms/report/proforma_report_templates.xml b/pms/report/proforma_report_templates.xml new file mode 100644 index 0000000000..bc8ac6a2cc --- /dev/null +++ b/pms/report/proforma_report_templates.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/pms/report/traveller_report_action.xml b/pms/report/traveller_report_action.xml new file mode 100644 index 0000000000..6ea2d6a057 --- /dev/null +++ b/pms/report/traveller_report_action.xml @@ -0,0 +1,14 @@ + + + + Traveller Report + pms.checkin.partner + qweb-pdf + pms.traveller_report + pms.traveller_report + + (object.state in ('draft', 'sent') and 'Quotation - %s' % (object.name)) or 'Order - %s' % (object.name) + + diff --git a/pms/security/ir.model.access.csv b/pms/security/ir.model.access.csv new file mode 100644 index 0000000000..9500e98e45 --- /dev/null +++ b/pms/security/ir.model.access.csv @@ -0,0 +1,71 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +user_access_pms_ubication,user_access_pms_ubication,model_pms_ubication,pms.group_pms_user,1,0,0,0 +user_access_pms_amenity,user_access_pms_amenity,model_pms_amenity,pms.group_pms_user,1,0,0,0 +user_access_pms_amenity_type,user_access_pms_amenity_type,model_pms_amenity_type,pms.group_pms_user,1,0,0,0 +user_access_pms_service,user_access_pms_service,model_pms_service,pms.group_pms_user,1,1,1,1 +user_access_pms_reservation_line,user_access_pms_reservation_line,model_pms_reservation_line,pms.group_pms_user,1,1,1,1 +user_access_room_closure_reason,user_access_room_closure_reason,model_room_closure_reason,pms.group_pms_user,1,0,0,0 +user_access_pms_service_line,user_access_pms_service_line,model_pms_service_line,pms.group_pms_user,1,1,1,1 +user_access_pms_board_service,user_access_pms_board_service,model_pms_board_service,pms.group_pms_user,1,0,0,0 +user_access_pms_checkin_partner,user_access_pms_checkin_partner,model_pms_checkin_partner,pms.group_pms_user,1,1,1,1 +user_access_pms_room_type_class,user_access_pms_room_type_class,model_pms_room_type_class,pms.group_pms_user,1,0,0,0 +user_access_pms_room,user_access_pms_room,model_pms_room,pms.group_pms_user,1,0,0,0 +user_access_pms_availability_plan_rule,user_access_pms_availability_plan_rule,model_pms_availability_plan_rule,pms.group_pms_user,1,0,0,0 +user_access_pms_availability,user_access_pms_availability,model_pms_availability,pms.group_pms_user,1,1,1,0 +user_access_pms_reservation,user_access_pms_reservation,model_pms_reservation,pms.group_pms_user,1,1,1,0 +user_access_pms_folio,user_access_pms_folio,model_pms_folio,pms.group_pms_user,1,1,1,1 +user_access_pms_room_type,user_access_pms_room_type,model_pms_room_type,pms.group_pms_user,1,0,0,0 +user_access_pms_board_service_room_type,user_access_pms_board_service_room_type,model_pms_board_service_room_type,pms.group_pms_user,1,0,0,0 +user_access_pms_board_service_room_type_line,user_access_pms_board_service_room_type_line,model_pms_board_service_room_type_line,pms.group_pms_user,1,0,0,0 +user_access_pms_board_service_line,user_access_pms_board_service_line,model_pms_board_service_line,pms.group_pms_user,1,0,0,0 +user_access_account_partial_reconcile,user_access_account_partial_reconcile,account.model_account_partial_reconcile,pms.group_pms_user,1,1,1,1 +user_access_pms_cancelation_rule,user_access_pms_cancelation_rule,model_pms_cancelation_rule,pms.group_pms_user,1,0,0,0 +user_access_account_full_reconcile,user_access_account_full_reconcile,account.model_account_full_reconcile,pms.group_pms_user,1,1,1,1 +user_access_property,user_access_property,model_pms_property,pms.group_pms_user,1,0,0,0 +user_access_availability,user_access_availability,model_pms_availability_plan,pms.group_pms_user,1,0,0,0 +user_access_pms_sale_channel,user_access_pms_sale_channel,model_pms_sale_channel,pms.group_pms_user,1,0,0,0 +user_access_pms_team_member,user_access_pms_team_member,model_pms_team_member,pms.group_pms_user,1,0,0,0 +manager_access_pms_ubication,manager_access_pms_ubication,model_pms_ubication,pms.group_pms_manager,1,1,1,1 +manager_access_pms_amenity,manager_access_pms_amenity,model_pms_amenity,pms.group_pms_manager,1,1,1,1 +manager_access_pms_amenity_type,manager_access_pms_amenity_type,model_pms_amenity_type,pms.group_pms_manager,1,1,1,1 +manager_access_pms_service,manager_access_pms_service,model_pms_service,pms.group_pms_manager,1,1,1,1 +manager_access_pms_reservation_line,manager_access_pms_reservation_line,model_pms_reservation_line,pms.group_pms_manager,1,1,1,1 +manager_access_room_closure_reason,manager_access_room_closure_reason,model_room_closure_reason,pms.group_pms_manager,1,1,1,1 +manager_access_pms_service_line,manager_access_pms_service_line,model_pms_service_line,pms.group_pms_manager,1,1,1,1 +manager_access_pms_board_service,manager_access_pms_board_service,model_pms_board_service,pms.group_pms_manager,1,1,1,1 +manager_access_pms_checkin_partner,manager_access_pms_checkin_partner,model_pms_checkin_partner,pms.group_pms_manager,1,1,1,1 +manager_access_pms_room_type_class,manager_access_pms_room_type_class,model_pms_room_type_class,pms.group_pms_manager,1,1,1,1 +manager_access_pms_room,manager_access_pms_room,model_pms_room,pms.group_pms_manager,1,1,1,1 +manager_access_pms_availability_plan_rule,manager_access_pms_availability_plan_rule,model_pms_availability_plan_rule,pms.group_pms_manager,1,1,1,1 +manager_access_pms_reservation,manager_access_pms_reservation,model_pms_reservation,pms.group_pms_manager,1,1,1,0 +manager_access_pms_availability,manager_access_pms_availability,model_pms_availability,pms.group_pms_manager,1,1,1,0 +manager_access_pms_folio,manager_access_pms_folio,model_pms_folio,pms.group_pms_manager,1,1,1,1 +manager_access_pms_room_type,manager_access_pms_room_type,model_pms_room_type,pms.group_pms_manager,1,1,1,1 +manager_access_pms_board_service_room_type,manager_access_pms_board_service_room_type,model_pms_board_service_room_type,pms.group_pms_manager,1,1,1,1 +manager_access_pms_board_service_room_type_line,manager_access_pms_board_service_room_type_line,model_pms_board_service_room_type_line,pms.group_pms_manager,1,1,1,1 +manager_access_pms_board_service_line,manager_access_pms_board_service_line,model_pms_board_service_line,pms.group_pms_manager,1,1,1,1 +manager_access_property,manager_access_property,model_pms_property,pms.group_pms_manager,1,1,1,1 +manager_access_pms_cancelation_rule,manager_access_pms_cancelation_rule,model_pms_cancelation_rule,pms.group_pms_manager,1,1,1,1 +manager_access_availability,manager_access_availability,model_pms_availability_plan,pms.group_pms_manager,1,1,1,1 +manager_access_pms_sale_channel,manager_access_pms_sale_channel,model_pms_sale_channel,pms.group_pms_manager,1,1,1,1 +manager_access_pms_team_member,manager_access_pms_team_member,model_pms_team_member,pms.group_pms_manager,1,1,1,1 +user_access_pms_reservation_split_join_swap_wizard,user_access_pms_reservation_split_join_swap_wizard,model_pms_reservation_split_join_swap_wizard,pms.group_pms_user,1,1,1,1 +user_access_pms_wizard_reservation_lines_split,user_access_pms_wizard_reservation_lines_split,model_pms_wizard_reservation_lines_split,pms.group_pms_user,1,1,1,1 +user_access_pms_massive_changes_wizard,user_access_pms_massive_changes_wizard,model_pms_massive_changes_wizard,pms.group_pms_user,1,1,1,1 +user_access_pms_advanced_filters_wizard,user_access_pms_advanced_filters_wizard,model_pms_advanced_filters_wizard,pms.group_pms_user,1,1,1,1 +user_access_pms_booking_engine,user_access_pms_booking_engine,model_pms_booking_engine,pms.group_pms_user,1,1,1,1 +user_access_pms_folio_availability_wizard,user_access_pms_folio_availability_wizard,model_pms_folio_availability_wizard,pms.group_pms_user,1,1,1,1 +user_access_pms_num_rooms_selection,user_access_pms_num_rooms_selection,model_pms_num_rooms_selection,pms.group_pms_user,1,1,1,1 +user_access_pms_folio_sale_line,user_access_pms_folio_sale_line,model_folio_sale_line,pms.group_pms_user,1,1,1,1 +user_access_folio_make_invoice_advance,user_access_folio_make_invoice_advance,model_folio_advance_payment_inv,pms.group_pms_user,1,1,1,1 +user_access_wizard_payment_folio,user_access_wizard_payment_folio,model_wizard_payment_folio,pms.group_pms_user,1,1,1,1 +user_access_wizard_folio_changes,user_access_wizard_folio_changes,model_wizard_folio_changes,pms.group_pms_user,1,1,1,1 +user_access_pms_folio_portal,user_access_pms_folio_portal,model_pms_folio,base.group_portal,1,0,0,0 +user_access_pms_reservation_portal,user_access_pms_reservation_portal,model_pms_reservation,base.group_portal,1,0,0,0 +user_access_pms_automated_mails,user_access_pms_automated_mails,model_pms_automated_mails,pms.group_pms_user,1,1,1,1 +access_pms_several_partners_wizard,access_pms_several_partners_wizard,model_pms_several_partners_wizard,base.group_user,1,1,1,1 +user_access_res_partner_portal,user_access_res_partner_portal,model_res_partner,base.group_portal,1,1,1,1 +user_access_pms_precheckin_portal,user_access_pms_precheckin_portal,model_pms_checkin_partner,base.group_portal,1,1,1,1 +user_access_pms_booking_duplicate,user_access_pms_booking_duplicate,model_pms_booking_duplicate,pms.group_pms_user,1,1,1,1 +user_access_pms_reservation_duplicate,user_access_pms_reservation_duplicate,model_pms_reservation_duplicate,pms.group_pms_user,1,1,1,1 +user_access_ir_pms_property,user_access_ir_pms_property,model_ir_pms_property,pms.group_pms_user,1,1,1,1 diff --git a/pms/security/pms_security.xml b/pms/security/pms_security.xml new file mode 100644 index 0000000000..e5d0cfa2da --- /dev/null +++ b/pms/security/pms_security.xml @@ -0,0 +1,271 @@ + + + + + + Property Management / User + + + + Property Management/ Manager + + + + + Property Management / CallCenter + + + + + + PMS Folio Company Rule + + + + ['|',('company_id','=',False),('company_id', 'in', company_ids)] + + + + PMS Reservation Company Rule + + + + ['|',('company_id','=',False),('company_id', 'in', company_ids)] + + + + + PMS Folio Property Rule + + + + ['|',('pms_property_id','=',False),('pms_property_id', 'in', + user.get_active_property_ids())] + + + + PMS Reservation Property Rule + + + + ['|',('pms_property_id','=',False),('pms_property_id', 'in', + user.get_active_property_ids())] + + + + PMS Checkin Property Rule + + + + ['|',('pms_property_id','=',False),('pms_property_id', 'in', + user.get_active_property_ids())] + + + + PMS Amenity Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + + + PMS Amenity Type Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + + + PMS Board Service Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + + + PMS Board Service Line Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + + + PMS Board Service Room Type Property Rule + + + + ['|',('pms_property_id','=',False),('pms_property_id', 'in', + user.get_active_property_ids())] + + + + PMS Cancelation Rule Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + + + PMS Ubication Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + + + PMS Reservation Line Property Rule + + + + ['|',('pms_property_id','=',False),('pms_property_id', 'in', + user.get_active_property_ids())] + + + + PMS Clousure Reason Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + + + PMS Room Type Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + + + PMS Room Type Availability Plan Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + + + PMS Room Type Availability Rule Property Rule + + + + ['|',('pms_property_id','=',False),('pms_property_id', 'in', + user.get_active_property_ids())] + + + + PMS Service Property Rule + + + + ['|',('pms_property_id','=',False),('pms_property_id', 'in', + user.get_active_property_ids())] + + + + PMS Service Line Property Rule + + + + ['|',('pms_property_id','=',False),('pms_property_id', 'in', + user.get_active_property_ids())] + + + + PMS Product Pricelist Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + + + PMS Room Type Class Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + + + PMS Users Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + + + PMS Account Move Property Rule + + + + ['|',('pms_property_id','=',False),('pms_property_id', 'in', + user.get_active_property_ids())] + + + + PMS Account Journal Property Rule + + + + ['|',('pms_property_ids','=',False),('pms_property_ids', 'in', + user.get_active_property_ids())] + + + + Portal Personal Folios + + [] + + + + + Portal Personal Reservation + + [] + + + + + Portal Personal Pre-checkin + + [] + + + + + Res Partner Rule + + [] + + + + + Portal Checkin Partner Rule + + [] + + + + + diff --git a/pms/static/description/avatar.png b/pms/static/description/avatar.png new file mode 100644 index 0000000000..dff716c281 Binary files /dev/null and b/pms/static/description/avatar.png differ diff --git a/pms/static/description/icon.png b/pms/static/description/icon.png new file mode 100644 index 0000000000..a81dd64c38 Binary files /dev/null and b/pms/static/description/icon.png differ diff --git a/pms/static/description/index.html b/pms/static/description/index.html new file mode 100644 index 0000000000..4a62de4527 --- /dev/null +++ b/pms/static/description/index.html @@ -0,0 +1,454 @@ + + + + + +PMS (Property Management System) + + + +
+

PMS (Property Management System)

+ + +

Beta License: AGPL-3 OCA/pms Translate me on Weblate Try me on Runboat

+

This module is an all-in-one property management system (PMS) focused on medium-sized properties +for managing every aspect of your property’s daily operations.

+

You can manage properties with multi-property and multi-company support, including your rooms inventory, +reservations, check-in, daily reports, board services, rate and availability plans among other property functionalities.

+

Table of contents

+ +
+

Installation

+

This module depends on modules base, mail, sale and multi_pms_properties. +Ensure yourself to have all them in your addons list.

+
+
+

Configuration

+

You will find the hotel settings in PMS Management > Configuration > Properties > Your Property.

+

This module required additional configuration for company, accounting, invoicing and user privileges.

+
+
+

Usage

+

To use this module, please, read the complete user guide at roomdoo.com.

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Commit [Sun]
  • +
+
+
+

Contributors

+
    +
  • Alexandre Díaz
  • +
  • Pablo Quesada
  • +
  • Jose Luis Algara
  • +
  • Commit [Sun] <https://www.commitsun.com>:
      +
    • Dario Lodeiros
    • +
    • Eric Antones
    • +
    • Sara Lago
    • +
    • Brais Abeijon
    • +
    • Miguel Padin
    • +
    +
  • +
  • Omar Castiñeira <omar@comunitea.com>
  • +
+
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/pms project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/pms/static/img/property_logo.png b/pms/static/img/property_logo.png new file mode 100755 index 0000000000..f2fcc2a487 Binary files /dev/null and b/pms/static/img/property_logo.png differ diff --git a/pms/static/src/js/folio_portal_sidebar.js b/pms/static/src/js/folio_portal_sidebar.js new file mode 100644 index 0000000000..629f71f899 --- /dev/null +++ b/pms/static/src/js/folio_portal_sidebar.js @@ -0,0 +1,74 @@ +odoo.define("account.FolioPortalSidebar", function (require) { + "use strict"; + + const dom = require("web.dom"); + var publicWidget = require("web.public.widget"); + var PortalSidebar = require("portal.PortalSidebar"); + var utils = require("web.utils"); + + publicWidget.registry.FolioPortalSidebar = PortalSidebar.extend({ + selector: ".o_portal_folio_sidebar", + events: { + "click .o_portal_folio_print": "_onPrintFolio", + }, + + /** + * @override + */ + start: function () { + var def = this._super.apply(this, arguments); + + var $folioHtml = this.$el.find("iframe#folio_html"); + var updateIframeSize = this._updateIframeSize.bind(this, $folioHtml); + + $(window).on("resize", updateIframeSize); + + var iframeDoc = + $folioHtml[0].contentDocument || $folioHtml[0].contentWindow.document; + if (iframeDoc.readyState === "complete") { + updateIframeSize(); + } else { + $folioHtml.on("load", updateIframeSize); + } + + return def; + }, + + // -------------------------------------------------------------------------- + // Handlers + // -------------------------------------------------------------------------- + + /** + * Called when the iframe is loaded or the window is resized on customer portal. + * The goal is to expand the iframe height to display the full report without scrollbar. + * + * @private + * @param {Object} $el: the iframe + */ + _updateIframeSize: function ($el) { + var $wrapwrap = $el.contents().find("div#wrapwrap"); + // Set it to 0 first to handle the case where scrollHeight is too big for its content. + $el.height(0); + $el.height($wrapwrap[0].scrollHeight); + + // Scroll to the right place after iframe resize + if (!utils.isValidAnchor(window.location.hash)) { + return; + } + var $target = $(window.location.hash); + if (!$target.length) { + return; + } + dom.scrollTo($target[0], {duration: 0}); + }, + /** + * @private + * @param {MouseEvent} ev + */ + _onPrintFolio: function (ev) { + ev.preventDefault(); + var href = $(ev.currentTarget).attr("href"); + this._printIframeContent(href); + }, + }); +}); diff --git a/pms/static/src/js/inherited_abstract_web_client.js b/pms/static/src/js/inherited_abstract_web_client.js new file mode 100644 index 0000000000..56b9af5feb --- /dev/null +++ b/pms/static/src/js/inherited_abstract_web_client.js @@ -0,0 +1,40 @@ +odoo.define("pms.AbstractWebClient", function (require) { + "use strict"; + + var AbstractWebClient = require("web.AbstractWebClient"); + var session = require("web.session"); + var utils = require("web.utils"); + + return AbstractWebClient.include({ + start: function () { + var state = $.bbq.getState(); + var current_pms_property_id = + session.user_pms_properties.current_pms_property[0]; + if (!state.pms_pids) { + state.pms_pids = utils.get_cookie("pms_pids") + ? utils.get_cookie("pms_pids") + : String(current_pms_property_id); + } + var statePmsPropertyIDS = _.map( + state.pms_pids.split(","), + function (pms_pid) { + return parseInt(pms_pid, 10); + } + ); + var userPmsPropertyIDS = _.map( + session.user_pms_properties.allowed_pms_properties, + function (pms_property) { + return pms_property[0]; + } + ); + // Check that the user has access to all the companies + if (!_.isEmpty(_.difference(statePmsPropertyIDS, userPmsPropertyIDS))) { + state.pms_pids = String(current_pms_property_id); + statePmsPropertyIDS = [current_pms_property_id]; + } + session.user_context.allowed_pms_property_ids = statePmsPropertyIDS; + + return this._super.apply(this, arguments); + }, + }); +}); diff --git a/pms/static/src/js/pms_list_controller.js b/pms/static/src/js/pms_list_controller.js new file mode 100644 index 0000000000..74db950656 --- /dev/null +++ b/pms/static/src/js/pms_list_controller.js @@ -0,0 +1,41 @@ +odoo.define("booking.engine.tree", function (require) { + "use strict"; + var ListController = require("web.ListController"); + var ListView = require("web.ListView"); + var viewRegistry = require("web.view_registry"); + + function renderBookingEngineButton() { + if (this.$buttons) { + var self = this; + this.$buttons.on("click", ".o_button_booking_engine", function () { + self.do_action({ + name: "Booking Engine", + type: "ir.actions.act_window", + res_model: "pms.booking.engine", + target: "new", + views: [[false, "form"]], + context: {is_modal: true}, + }); + }); + } + } + + var BookingEngineRequestListController = ListController.extend({ + start: function () { + return this._super.apply(this, arguments); + }, + buttons_template: "BookingEngineRequestListView.buttons", + renderButtons: function () { + this._super.apply(this, arguments); + renderBookingEngineButton.apply(this, arguments); + }, + }); + + var BookingEngineRequestListView = ListView.extend({ + config: _.extend({}, ListView.prototype.config, { + Controller: BookingEngineRequestListController, + }), + }); + + viewRegistry.add("pms_booking_engine_request_tree", BookingEngineRequestListView); +}); diff --git a/pms/static/src/js/reconciliation_widget.js b/pms/static/src/js/reconciliation_widget.js new file mode 100644 index 0000000000..f0e49092e9 --- /dev/null +++ b/pms/static/src/js/reconciliation_widget.js @@ -0,0 +1,84 @@ +/* +Copyright 2022 Comunitea. +License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +*/ +odoo.define("account_reconciliation_widget_inherit", function (require) { + "use strict"; + + var core = require("web.core"); + var relational_fields = require("web.relational_fields"); + var ReconciliationRenderer = require("account.ReconciliationRenderer"); + var ReconciliationModel = require("account.ReconciliationModel"); + var _t = core._t; + + ReconciliationModel.StatementModel.include({ + init: function (parent, options) { + this._super(parent, options); + this.extra_field_names = ["pms_property_id"]; + this.extra_fields = [ + { + relation: "pms.property", + type: "many2one", + name: "pms_property_id", + }, + ]; + this.extra_fieldInfo = { + pms_property_id: {string: _t("Property")}, + }; + this.quickCreateFields = this.quickCreateFields.concat( + this.extra_field_names + ); + }, + + makeRecord: function (model, fields, fieldInfo) { + if (model === "account.bank.statement.line") { + var fields = fields.concat(this.extra_fields); + _.extend(fieldInfo, this.extra_fieldInfo); + } + return this._super(model, fields, fieldInfo); + }, + + _formatToProcessReconciliation: function (line, prop) { + var result = this._super(line, prop); + if (prop.pms_property_id) result.pms_property_id = prop.pms_property_id.id; + return result; + }, + + _formatQuickCreate: function (line, values) { + var prop = this._super(line, values); + prop.pms_property_id = ""; + return prop; + }, + }); + + ReconciliationRenderer.LineRenderer.include({ + _renderCreate: function (state) { + return Promise.all([this._super(state), this._makePmsPropertyRecord()]); + }, + + _makePmsPropertyRecord: function () { + const field = { + type: "many2one", + name: "pms_property_id", + relation: "pms.property", + }; + return this.model + .makeRecord("account.bank.statement.line", [field], { + pms_property_id: {}, + }) + .then((recordID) => { + this.fields.pms_property_id = new relational_fields.FieldMany2One( + this, + "pms_property_id", + this.model.get(recordID), + { + mode: "edit", + } + ); + this.fields.pms_property_id.appendTo( + this.$(".create_pms_property_id .o_td_field") + ); + }); + }, + }); +}); diff --git a/pms/static/src/js/send_invitation_data.js b/pms/static/src/js/send_invitation_data.js new file mode 100644 index 0000000000..b5eb4e9645 --- /dev/null +++ b/pms/static/src/js/send_invitation_data.js @@ -0,0 +1,68 @@ +odoo.define("pms.SendInvitationData", function (require) { + "use strict"; + + require("web.dom_ready"); + var publicWidget = require("web.public.widget"); + + publicWidget.registry.SendInvitationData = publicWidget.Widget.extend({ + selector: ".o_send_invitation_js", + events: { + click: "_onReminderToggleClick", + }, + + _onReminderToggleClick: function (ev) { + ev.preventDefault(); + var checkinPartnerId = $(ev.currentTarget) + .parent() + .parent() + .parent() + .find("input[name=checkin_partner_id]") + .val(); + var firstname = $(ev.currentTarget) + .parent() + .parent() + .find("input[name=invitation_firstname]") + .val(); + var email = $(ev.currentTarget) + .parent() + .parent() + .find("input[name=invitation_email]") + .val(); + var error_firstname = $(ev.currentTarget) + .parent() + .parent() + .find("span:first"); + var error_email = $(ev.currentTarget) + .parent() + .parent() + .find("input[name=invitation_email]") + .siblings("span"); + console.log(error_firstname); + console.log(error_email); + if (firstname === "" || email === "") { + if (firstname === "") { + error_firstname.removeClass("d-none"); + } else { + error_firstname.addClass("d-none"); + } + if (email === "") { + error_email.removeClass("d-none"); + } else { + error_email.addClass("d-none"); + } + } else { + error_firstname.addClass("d-none"); + error_email.addClass("d-none"); + this._rpc({ + route: "/my/precheckin/send_invitation", + params: { + checkin_partner_id: checkinPartnerId, + firstname: firstname, + email: email, + }, + }); + } + }, + }); + return publicWidget.registry.SendInvitationData; +}); diff --git a/pms/static/src/js/session.js b/pms/static/src/js/session.js new file mode 100644 index 0000000000..8bc2d57de6 --- /dev/null +++ b/pms/static/src/js/session.js @@ -0,0 +1,26 @@ +odoo.define("pms.session", function (require) { + "use strict"; + + var Session = require("web.Session"); + var utils = require("web.utils"); + + Session.include({ + // TODO: require test and debug + setPmsProperties: function (pms_main_property_id, pms_property_ids) { + var hash = $.bbq.getState(); + hash.pms_pids = pms_property_ids + .sort(function (a, b) { + if (a === pms_main_property_id) { + return -1; + } else if (b === pms_main_property_id) { + return 1; + } + return a - b; + }) + .join(","); + utils.set_cookie("pms_pids", hash.pms_pids || String(pms_main_property_id)); + $.bbq.pushState({pms_pids: hash.pms_pids}, 0); + location.reload(); + }, + }); +}); diff --git a/pms/static/src/js/widgets/datepicker/bootstrap-datepicker.js b/pms/static/src/js/widgets/datepicker/bootstrap-datepicker.js new file mode 100644 index 0000000000..4f45b56314 --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/bootstrap-datepicker.js @@ -0,0 +1,1197 @@ +/* ========================================================= + * bootstrap-datepicker.js + * http://www.eyecon.ro/bootstrap-datepicker + * ========================================================= + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================= */ + +!(function ($) { + function UTCDate() { + return new Date(Date.UTC.apply(Date, arguments)); + } + function UTCToday() { + var today = new Date(); + return UTCDate(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate()); + } + + // Picker object + + var Datepicker = function (element, options) { + var that = this; + + this.element = $(element); + this.language = options.language || this.element.data("date-language") || "en"; + this.language = this.language in dates ? this.language : "en"; + this.isRTL = dates[this.language].rtl || false; + this.format = DPGlobal.parseFormat( + options.format || this.element.data("date-format") || "mm/dd/yyyy" + ); + this.isInline = false; + this.isInput = this.element.is("input"); + this.component = this.element.is(".date") + ? this.element.find(".add-on") + : false; + this.hasInput = this.component && this.element.find("input").length; + if (this.component && this.component.length === 0) this.component = false; + + this._attachEvents(); + + this.forceParse = true; + if ("forceParse" in options) { + this.forceParse = options.forceParse; + } else if ("dateForceParse" in this.element.data()) { + this.forceParse = this.element.data("date-force-parse"); + } + + this.picker = $(DPGlobal.template) + .appendTo(this.isInline ? this.element : "body") + .on({ + click: $.proxy(this.click, this), + mousedown: $.proxy(this.mousedown, this), + }); + + if (this.isInline) { + this.picker.addClass("datepicker-inline"); + } else { + this.picker.addClass("datepicker-dropdown dropdown-menu"); + } + if (this.isRTL) { + this.picker.addClass("datepicker-rtl"); + this.picker + .find(".prev i, .next i") + .toggleClass("icon-arrow-left icon-arrow-right"); + } + $(document).on("mousedown", function (e) { + // Clicked outside the datepicker, hide it + if ($(e.target).closest(".datepicker").length === 0) { + that.hide(); + } + }); + + this.autoclose = false; + if ("autoclose" in options) { + this.autoclose = options.autoclose; + } else if ("dateAutoclose" in this.element.data()) { + this.autoclose = this.element.data("date-autoclose"); + } + + this.keyboardNavigation = true; + if ("keyboardNavigation" in options) { + this.keyboardNavigation = options.keyboardNavigation; + } else if ("dateKeyboardNavigation" in this.element.data()) { + this.keyboardNavigation = this.element.data("date-keyboard-navigation"); + } + + this.viewMode = this.startViewMode = 0; + switch (options.startView || this.element.data("date-start-view")) { + case 2: + case "decade": + this.viewMode = this.startViewMode = 2; + break; + case 1: + case "year": + this.viewMode = this.startViewMode = 1; + break; + } + + this.todayBtn = + options.todayBtn || this.element.data("date-today-btn") || false; + this.todayHighlight = + options.todayHighlight || + this.element.data("date-today-highlight") || + false; + + this.weekStart = + (options.weekStart || + this.element.data("date-weekstart") || + dates[this.language].weekStart || + 0) % 7; + this.weekEnd = (this.weekStart + 6) % 7; + this.startDate = -Infinity; + this.endDate = Infinity; + this.daysOfWeekDisabled = []; + this.setStartDate(options.startDate || this.element.data("date-startdate")); + this.setEndDate(options.endDate || this.element.data("date-enddate")); + this.setDaysOfWeekDisabled( + options.daysOfWeekDisabled || + this.element.data("date-days-of-week-disabled") + ); + this.fillDow(); + this.fillMonths(); + this.update(); + this.showMode(); + + if (this.isInline) { + this.show(); + } + }; + + Datepicker.prototype = { + constructor: Datepicker, + + _events: [], + _attachEvents: function () { + this._detachEvents(); + if (this.isInput) { + // Single input + this._events = [ + [ + this.element, + { + focus: $.proxy(this.show, this), + keyup: $.proxy(this.update, this), + keydown: $.proxy(this.keydown, this), + }, + ], + ]; + } else if (this.component && this.hasInput) { + // Component: input + button + this._events = [ + // For components that are not readonly, allow keyboard nav + [ + this.element.find("input"), + { + focus: $.proxy(this.show, this), + keyup: $.proxy(this.update, this), + keydown: $.proxy(this.keydown, this), + }, + ], + [ + this.component, + { + click: $.proxy(this.show, this), + }, + ], + ]; + } else if (this.element.is("div")) { + // Inline datepicker + this.isInline = true; + } else { + this._events = [ + [ + this.element, + { + click: $.proxy(this.show, this), + }, + ], + ]; + } + for (var i = 0, el, ev; i < this._events.length; i++) { + el = this._events[i][0]; + ev = this._events[i][1]; + el.on(ev); + } + }, + _detachEvents: function () { + for (var i = 0, el, ev; i < this._events.length; i++) { + el = this._events[i][0]; + ev = this._events[i][1]; + el.off(ev); + } + this._events = []; + }, + + show: function (e) { + this.picker.show(); + this.height = this.component + ? this.component.outerHeight() + : this.element.outerHeight(); + this.update(); + this.place(); + $(window).on("resize", $.proxy(this.place, this)); + if (e) { + e.stopPropagation(); + e.preventDefault(); + } + this.element.trigger({ + type: "show", + date: this.date, + }); + }, + + hide: function (e) { + if (this.isInline) return; + this.picker.hide(); + $(window).off("resize", this.place); + this.viewMode = this.startViewMode; + this.showMode(); + if (!this.isInput) { + $(document).off("mousedown", this.hide); + } + + if ( + this.forceParse && + ((this.isInput && this.element.val()) || + (this.hasInput && this.element.find("input").val())) + ) + this.setValue(); + this.element.trigger({ + type: "hide", + date: this.date, + }); + }, + + remove: function () { + this._detachEvents(); + this.picker.remove(); + delete this.element.data().datepicker; + }, + + getDate: function () { + var d = this.getUTCDate(); + return new Date(d.getTime() + d.getTimezoneOffset() * 60000); + }, + + getUTCDate: function () { + return this.date; + }, + + setDate: function (d) { + this.setUTCDate(new Date(d.getTime() - d.getTimezoneOffset() * 60000)); + }, + + setUTCDate: function (d) { + this.date = d; + this.setValue(); + }, + + setValue: function () { + var formatted = this.getFormattedDate(); + if (!this.isInput) { + if (this.component) { + this.element.find("input").val(formatted); + } + this.element.data("date", formatted); + } else { + this.element.val(formatted); + } + }, + + getFormattedDate: function (format) { + if (format === undefined) format = this.format; + return DPGlobal.formatDate(this.date, format, this.language); + }, + + setStartDate: function (startDate) { + this.startDate = startDate || -Infinity; + if (this.startDate !== -Infinity) { + this.startDate = DPGlobal.parseDate( + this.startDate, + this.format, + this.language + ); + } + this.update(); + this.updateNavArrows(); + }, + + setEndDate: function (endDate) { + this.endDate = endDate || Infinity; + if (this.endDate !== Infinity) { + this.endDate = DPGlobal.parseDate( + this.endDate, + this.format, + this.language + ); + } + this.update(); + this.updateNavArrows(); + }, + + setDaysOfWeekDisabled: function (daysOfWeekDisabled) { + this.daysOfWeekDisabled = daysOfWeekDisabled || []; + if (!$.isArray(this.daysOfWeekDisabled)) { + this.daysOfWeekDisabled = this.daysOfWeekDisabled.split(/,\s*/); + } + this.daysOfWeekDisabled = $.map(this.daysOfWeekDisabled, function (d) { + return parseInt(d, 10); + }); + this.update(); + this.updateNavArrows(); + }, + + place: function () { + if (this.isInline) return; + var zIndex = + parseInt( + this.element + .parents() + .filter(function () { + return $(this).css("z-index") != "auto"; + }) + .first() + .css("z-index") + ) + 10; + var offset = this.component + ? this.component.offset() + : this.element.offset(); + var height = this.component + ? this.component.outerHeight(true) + : this.element.outerHeight(true); + this.picker.css({ + top: offset.top + height, + left: offset.left, + zIndex: zIndex, + }); + }, + + update: function () { + var date, + fromArgs = false; + if ( + arguments && + arguments.length && + (typeof arguments[0] === "string" || arguments[0] instanceof Date) + ) { + date = arguments[0]; + fromArgs = true; + } else { + date = this.isInput + ? this.element.val() + : this.element.data("date") || this.element.find("input").val(); + } + + this.date = DPGlobal.parseDate(date, this.format, this.language); + + if (fromArgs) this.setValue(); + + var oldViewDate = this.viewDate; + if (this.date < this.startDate) { + this.viewDate = new Date(this.startDate); + } else if (this.date > this.endDate) { + this.viewDate = new Date(this.endDate); + } else { + this.viewDate = new Date(this.date); + } + + if (oldViewDate && oldViewDate.getTime() != this.viewDate.getTime()) { + this.element.trigger({ + type: "changeDate", + date: this.viewDate, + }); + } + this.fill(); + }, + + fillDow: function () { + var dowCnt = this.weekStart, + html = ""; + while (dowCnt < this.weekStart + 7) { + html += + '' + + dates[this.language].daysMin[dowCnt++ % 7] + + ""; + } + html += ""; + this.picker.find(".datepicker-days thead").append(html); + }, + + fillMonths: function () { + var html = "", + i = 0; + while (i < 12) { + html += + '' + + dates[this.language].monthsShort[i++] + + ""; + } + this.picker.find(".datepicker-months td").html(html); + }, + + fill: function () { + var d = new Date(this.viewDate), + year = d.getUTCFullYear(), + month = d.getUTCMonth(), + startYear = + this.startDate !== -Infinity + ? this.startDate.getUTCFullYear() + : -Infinity, + startMonth = + this.startDate !== -Infinity + ? this.startDate.getUTCMonth() + : -Infinity, + endYear = + this.endDate !== Infinity + ? this.endDate.getUTCFullYear() + : Infinity, + endMonth = + this.endDate !== Infinity ? this.endDate.getUTCMonth() : Infinity, + currentDate = this.date && this.date.valueOf(), + today = new Date(); + this.picker + .find(".datepicker-days thead th:eq(1)") + .text(dates[this.language].months[month] + " " + year); + this.picker + .find("tfoot th.today") + .text(dates[this.language].today) + .toggle(this.todayBtn !== false); + this.updateNavArrows(); + this.fillMonths(); + var prevMonth = UTCDate(year, month - 1, 28, 0, 0, 0, 0), + day = DPGlobal.getDaysInMonth( + prevMonth.getUTCFullYear(), + prevMonth.getUTCMonth() + ); + prevMonth.setUTCDate(day); + prevMonth.setUTCDate( + day - ((prevMonth.getUTCDay() - this.weekStart + 7) % 7) + ); + var nextMonth = new Date(prevMonth); + nextMonth.setUTCDate(nextMonth.getUTCDate() + 42); + nextMonth = nextMonth.valueOf(); + var html = []; + var clsName; + while (prevMonth.valueOf() < nextMonth) { + if (prevMonth.getUTCDay() == this.weekStart) { + html.push(""); + } + clsName = ""; + if ( + prevMonth.getUTCFullYear() < year || + (prevMonth.getUTCFullYear() == year && + prevMonth.getUTCMonth() < month) + ) { + clsName += " old"; + } else if ( + prevMonth.getUTCFullYear() > year || + (prevMonth.getUTCFullYear() == year && + prevMonth.getUTCMonth() > month) + ) { + clsName += " new"; + } + // Compare internal UTC date with local today, not UTC today + if ( + this.todayHighlight && + prevMonth.getUTCFullYear() == today.getFullYear() && + prevMonth.getUTCMonth() == today.getMonth() && + prevMonth.getUTCDate() == today.getDate() + ) { + clsName += " today"; + } + if (currentDate && prevMonth.valueOf() == currentDate) { + clsName += " active"; + } + if ( + prevMonth.valueOf() < this.startDate || + prevMonth.valueOf() > this.endDate || + $.inArray(prevMonth.getUTCDay(), this.daysOfWeekDisabled) !== -1 + ) { + clsName += " disabled"; + } + html.push( + '' + prevMonth.getUTCDate() + "" + ); + if (prevMonth.getUTCDay() == this.weekEnd) { + html.push(""); + } + prevMonth.setUTCDate(prevMonth.getUTCDate() + 1); + } + this.picker.find(".datepicker-days tbody").empty().append(html.join("")); + var currentYear = this.date && this.date.getUTCFullYear(); + + var months = this.picker + .find(".datepicker-months") + .find("th:eq(1)") + .text(year) + .end() + .find("span") + .removeClass("active"); + if (currentYear && currentYear == year) { + months.eq(this.date.getUTCMonth()).addClass("active"); + } + if (year < startYear || year > endYear) { + months.addClass("disabled"); + } + if (year == startYear) { + months.slice(0, startMonth).addClass("disabled"); + } + if (year == endYear) { + months.slice(endMonth + 1).addClass("disabled"); + } + + html = ""; + year = parseInt(year / 10, 10) * 10; + var yearCont = this.picker + .find(".datepicker-years") + .find("th:eq(1)") + .text(year + "-" + (year + 9)) + .end() + .find("td"); + year -= 1; + for (var i = -1; i < 11; i++) { + html += + ' endYear ? " disabled" : "") + + '">' + + year + + ""; + year += 1; + } + yearCont.html(html); + }, + + updateNavArrows: function () { + var d = new Date(this.viewDate), + year = d.getUTCFullYear(), + month = d.getUTCMonth(); + switch (this.viewMode) { + case 0: + if ( + this.startDate !== -Infinity && + year <= this.startDate.getUTCFullYear() && + month <= this.startDate.getUTCMonth() + ) { + this.picker.find(".prev").css({visibility: "hidden"}); + } else { + this.picker.find(".prev").css({visibility: "visible"}); + } + if ( + this.endDate !== Infinity && + year >= this.endDate.getUTCFullYear() && + month >= this.endDate.getUTCMonth() + ) { + this.picker.find(".next").css({visibility: "hidden"}); + } else { + this.picker.find(".next").css({visibility: "visible"}); + } + break; + case 1: + case 2: + if ( + this.startDate !== -Infinity && + year <= this.startDate.getUTCFullYear() + ) { + this.picker.find(".prev").css({visibility: "hidden"}); + } else { + this.picker.find(".prev").css({visibility: "visible"}); + } + if ( + this.endDate !== Infinity && + year >= this.endDate.getUTCFullYear() + ) { + this.picker.find(".next").css({visibility: "hidden"}); + } else { + this.picker.find(".next").css({visibility: "visible"}); + } + break; + } + }, + + click: function (e) { + e.stopPropagation(); + e.preventDefault(); + var target = $(e.target).closest("span, td, th"); + if (target.length == 1) { + switch (target[0].nodeName.toLowerCase()) { + case "th": + switch (target[0].className) { + case "switch": + this.showMode(1); + break; + case "prev": + case "next": + var dir = + DPGlobal.modes[this.viewMode].navStep * + (target[0].className == "prev" ? -1 : 1); + switch (this.viewMode) { + case 0: + this.viewDate = this.moveMonth( + this.viewDate, + dir + ); + break; + case 1: + case 2: + this.viewDate = this.moveYear( + this.viewDate, + dir + ); + break; + } + this.fill(); + break; + case "today": + var date = new Date(); + date = UTCDate( + date.getFullYear(), + date.getMonth(), + date.getDate(), + 0, + 0, + 0 + ); + + this.showMode(-2); + var which = this.todayBtn == "linked" ? null : "view"; + this._setDate(date, which); + break; + } + break; + case "span": + if (!target.is(".disabled")) { + this.viewDate.setUTCDate(1); + if (target.is(".month")) { + var month = target.parent().find("span").index(target); + this.viewDate.setUTCMonth(month); + this.element.trigger({ + type: "changeMonth", + date: this.viewDate, + }); + } else { + var year = parseInt(target.text(), 10) || 0; + this.viewDate.setUTCFullYear(year); + this.element.trigger({ + type: "changeYear", + date: this.viewDate, + }); + } + this.showMode(-1); + this.fill(); + } + break; + case "td": + if (target.is(".day") && !target.is(".disabled")) { + var day = parseInt(target.text(), 10) || 1; + var year = this.viewDate.getUTCFullYear(), + month = this.viewDate.getUTCMonth(); + if (target.is(".old")) { + if (month === 0) { + month = 11; + year -= 1; + } else { + month -= 1; + } + } else if (target.is(".new")) { + if (month == 11) { + month = 0; + year += 1; + } else { + month += 1; + } + } + this._setDate(UTCDate(year, month, day, 0, 0, 0, 0)); + } + break; + } + } + }, + + _setDate: function (date, which) { + if (!which || which == "date") this.date = date; + if (!which || which == "view") this.viewDate = date; + this.fill(); + this.setValue(); + this.element.trigger({ + type: "changeDate", + date: this.date, + }); + var element; + if (this.isInput) { + element = this.element; + } else if (this.component) { + element = this.element.find("input"); + } + if (element) { + element.change(); + if (this.autoclose && (!which || which == "date")) { + this.hide(); + } + } + }, + + moveMonth: function (date, dir) { + if (!dir) return date; + var new_date = new Date(date.valueOf()), + day = new_date.getUTCDate(), + month = new_date.getUTCMonth(), + mag = Math.abs(dir), + new_month, + test; + dir = dir > 0 ? 1 : -1; + if (mag == 1) { + test = + dir == -1 + ? // If going back one month, make sure month is not current month + // (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02) + function () { + return new_date.getUTCMonth() == month; + } + : // If going forward one month, make sure month is as expected + // (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02) + function () { + return new_date.getUTCMonth() != new_month; + }; + new_month = month + dir; + new_date.setUTCMonth(new_month); + // Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11 + if (new_month < 0 || new_month > 11) new_month = (new_month + 12) % 12; + } else { + // For magnitudes >1, move one month at a time... + for (var i = 0; i < mag; i++) + // ...which might decrease the day (eg, Jan 31 to Feb 28, etc)... + new_date = this.moveMonth(new_date, dir); + // ...then reset the day, keeping it in the new month + new_month = new_date.getUTCMonth(); + new_date.setUTCDate(day); + test = function () { + return new_month != new_date.getUTCMonth(); + }; + } + // Common date-resetting loop -- if date is beyond end of month, make it + // end of month + while (test()) { + new_date.setUTCDate(--day); + new_date.setUTCMonth(new_month); + } + return new_date; + }, + + moveYear: function (date, dir) { + return this.moveMonth(date, dir * 12); + }, + + dateWithinRange: function (date) { + return date >= this.startDate && date <= this.endDate; + }, + + keydown: function (e) { + if (this.picker.is(":not(:visible)")) { + if (e.keyCode == 27) + // Allow escape to hide and re-show picker + this.show(); + return; + } + var dateChanged = false, + dir, + day, + month, + newDate, + newViewDate; + switch (e.keyCode) { + case 27: // Escape + this.hide(); + e.preventDefault(); + break; + case 37: // Left + case 39: // Right + if (!this.keyboardNavigation) break; + dir = e.keyCode == 37 ? -1 : 1; + if (e.ctrlKey) { + newDate = this.moveYear(this.date, dir); + newViewDate = this.moveYear(this.viewDate, dir); + } else if (e.shiftKey) { + newDate = this.moveMonth(this.date, dir); + newViewDate = this.moveMonth(this.viewDate, dir); + } else { + newDate = new Date(this.date); + newDate.setUTCDate(this.date.getUTCDate() + dir); + newViewDate = new Date(this.viewDate); + newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir); + } + if (this.dateWithinRange(newDate)) { + this.date = newDate; + this.viewDate = newViewDate; + this.setValue(); + this.update(); + e.preventDefault(); + dateChanged = true; + } + break; + case 38: // Up + case 40: // Down + if (!this.keyboardNavigation) break; + dir = e.keyCode == 38 ? -1 : 1; + if (e.ctrlKey) { + newDate = this.moveYear(this.date, dir); + newViewDate = this.moveYear(this.viewDate, dir); + } else if (e.shiftKey) { + newDate = this.moveMonth(this.date, dir); + newViewDate = this.moveMonth(this.viewDate, dir); + } else { + newDate = new Date(this.date); + newDate.setUTCDate(this.date.getUTCDate() + dir * 7); + newViewDate = new Date(this.viewDate); + newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir * 7); + } + if (this.dateWithinRange(newDate)) { + this.date = newDate; + this.viewDate = newViewDate; + this.setValue(); + this.update(); + e.preventDefault(); + dateChanged = true; + } + break; + case 13: // Enter + this.hide(); + e.preventDefault(); + break; + case 9: // Tab + this.hide(); + break; + } + if (dateChanged) { + this.element.trigger({ + type: "changeDate", + date: this.date, + }); + var element; + if (this.isInput) { + element = this.element; + } else if (this.component) { + element = this.element.find("input"); + } + if (element) { + element.change(); + } + } + }, + + showMode: function (dir) { + if (dir) { + this.viewMode = Math.max(0, Math.min(2, this.viewMode + dir)); + } + /* + Vitalets: fixing bug of very special conditions: + jquery 1.7.1 + webkit + show inline datepicker in bootstrap popover. + Method show() does not set display css correctly and datepicker is not shown. + Changed to .css('display', 'block') solve the problem. + See https://github.com/vitalets/x-editable/issues/37 + + In jquery 1.7.2+ everything works fine. + */ + // this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show(); + this.picker + .find(">div") + .hide() + .filter(".datepicker-" + DPGlobal.modes[this.viewMode].clsName) + .css("display", "block"); + this.updateNavArrows(); + }, + }; + + $.fn.datepicker = function (option) { + var args = Array.apply(null, arguments); + args.shift(); + return this.each(function () { + var $this = $(this), + data = $this.data("datepicker"), + options = typeof option === "object" && option; + if (!data) { + $this.data( + "datepicker", + (data = new Datepicker( + this, + $.extend({}, $.fn.datepicker.defaults, options) + )) + ); + } + if (typeof option === "string" && typeof data[option] === "function") { + data[option].apply(data, args); + } + }); + }; + + $.fn.datepicker.defaults = {}; + $.fn.datepicker.Constructor = Datepicker; + var dates = ($.fn.datepicker.dates = { + en: { + days: [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday", + ], + daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], + daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"], + months: [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ], + monthsShort: [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ], + today: "Today", + }, + }); + + var DPGlobal = { + modes: [ + { + clsName: "days", + navFnc: "Month", + navStep: 1, + }, + { + clsName: "months", + navFnc: "FullYear", + navStep: 1, + }, + { + clsName: "years", + navFnc: "FullYear", + navStep: 10, + }, + ], + isLeapYear: function (year) { + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; + }, + getDaysInMonth: function (year, month) { + return [ + 31, + DPGlobal.isLeapYear(year) ? 29 : 28, + 31, + 30, + 31, + 30, + 31, + 31, + 30, + 31, + 30, + 31, + ][month]; + }, + validParts: /dd?|DD?|mm?|MM?|yy(?:yy)?/g, + nonpunctuation: /[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g, + parseFormat: function (format) { + // IE treats \0 as a string end in inputs (truncating the value), + // so it's a bad format delimiter, anyway + var separators = format.replace(this.validParts, "\0").split("\0"), + parts = format.match(this.validParts); + if (!separators || !separators.length || !parts || parts.length === 0) { + throw new Error("Invalid date format."); + } + return {separators: separators, parts: parts}; + }, + parseDate: function (date, format, language) { + if (date instanceof Date) return date; + if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)) { + var part_re = /([\-+]\d+)([dmwy])/, + parts = date.match(/([\-+]\d+)([dmwy])/g), + part, + dir; + date = new Date(); + for (var i = 0; i < parts.length; i++) { + part = part_re.exec(parts[i]); + dir = parseInt(part[1]); + switch (part[2]) { + case "d": + date.setUTCDate(date.getUTCDate() + dir); + break; + case "m": + date = Datepicker.prototype.moveMonth.call( + Datepicker.prototype, + date, + dir + ); + break; + case "w": + date.setUTCDate(date.getUTCDate() + dir * 7); + break; + case "y": + date = Datepicker.prototype.moveYear.call( + Datepicker.prototype, + date, + dir + ); + break; + } + } + return UTCDate( + date.getUTCFullYear(), + date.getUTCMonth(), + date.getUTCDate(), + 0, + 0, + 0 + ); + } + var parts = (date && date.match(this.nonpunctuation)) || [], + date = new Date(), + parsed = {}, + setters_order = ["yyyy", "yy", "M", "MM", "m", "mm", "d", "dd"], + setters_map = { + yyyy: function (d, v) { + return d.setUTCFullYear(v); + }, + yy: function (d, v) { + return d.setUTCFullYear(2000 + v); + }, + m: function (d, v) { + v -= 1; + while (v < 0) v += 12; + v %= 12; + d.setUTCMonth(v); + while (d.getUTCMonth() != v) d.setUTCDate(d.getUTCDate() - 1); + return d; + }, + d: function (d, v) { + return d.setUTCDate(v); + }, + }, + val, + filtered, + part; + setters_map.M = setters_map.MM = setters_map.mm = setters_map.m; + setters_map.dd = setters_map.d; + date = UTCDate( + date.getFullYear(), + date.getMonth(), + date.getDate(), + 0, + 0, + 0 + ); + var fparts = format.parts.slice(); + // Remove noop parts + if (parts.length != fparts.length) { + fparts = $(fparts) + .filter(function (i, p) { + return $.inArray(p, setters_order) !== -1; + }) + .toArray(); + } + // Process remainder + if (parts.length == fparts.length) { + for (var i = 0, cnt = fparts.length; i < cnt; i++) { + val = parseInt(parts[i], 10); + part = fparts[i]; + if (isNaN(val)) { + switch (part) { + case "MM": + filtered = $(dates[language].months).filter( + function () { + var m = this.slice(0, parts[i].length), + p = parts[i].slice(0, m.length); + return m == p; + } + ); + val = + $.inArray(filtered[0], dates[language].months) + 1; + break; + case "M": + filtered = $(dates[language].monthsShort).filter( + function () { + var m = this.slice(0, parts[i].length), + p = parts[i].slice(0, m.length); + return m == p; + } + ); + val = + $.inArray( + filtered[0], + dates[language].monthsShort + ) + 1; + break; + } + } + parsed[part] = val; + } + for (var i = 0, s; i < setters_order.length; i++) { + s = setters_order[i]; + if (s in parsed && !isNaN(parsed[s])) + setters_map[s](date, parsed[s]); + } + } + return date; + }, + formatDate: function (date, format, language) { + var val = { + d: date.getUTCDate(), + D: dates[language].daysShort[date.getUTCDay()], + DD: dates[language].days[date.getUTCDay()], + m: date.getUTCMonth() + 1, + M: dates[language].monthsShort[date.getUTCMonth()], + MM: dates[language].months[date.getUTCMonth()], + yy: date.getUTCFullYear().toString().substring(2), + yyyy: date.getUTCFullYear(), + }; + val.dd = (val.d < 10 ? "0" : "") + val.d; + val.mm = (val.m < 10 ? "0" : "") + val.m; + var date = [], + seps = $.extend([], format.separators); + for (var i = 0, cnt = format.parts.length; i < cnt; i++) { + if (seps.length) date.push(seps.shift()); + date.push(val[format.parts[i]]); + } + return date.join(""); + }, + headTemplate: + "" + + "" + + '' + + '' + + '' + + "" + + "", + contTemplate: '', + footTemplate: '', + }; + DPGlobal.template = + '
' + + '
' + + '' + + DPGlobal.headTemplate + + "" + + DPGlobal.footTemplate + + "
" + + "
" + + '
' + + '' + + DPGlobal.headTemplate + + DPGlobal.contTemplate + + DPGlobal.footTemplate + + "
" + + "
" + + '
' + + '' + + DPGlobal.headTemplate + + DPGlobal.contTemplate + + DPGlobal.footTemplate + + "
" + + "
" + + "
"; + + $.fn.datepicker.DPGlobal = DPGlobal; +})(window.jQuery); diff --git a/pms/static/src/js/widgets/datepicker/datepicker.css b/pms/static/src/js/widgets/datepicker/datepicker.css new file mode 100644 index 0000000000..52af922532 --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/datepicker.css @@ -0,0 +1,278 @@ +.datepicker { + padding: 4px; + margin-top: 1px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + direction: ltr; +} +.datepicker-inline { + width: 220px; +} +.datepicker.datepicker-rtl { + direction: rtl; +} +.datepicker.datepicker-rtl table tr td span { + float: right; +} +.datepicker-dropdown { + top: 0; + left: 0; +} +.datepicker-dropdown:before { + content: ""; + display: inline-block; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid #ccc; + border-bottom-color: rgba(0, 0, 0, 0.2); + position: absolute; + top: -7px; + left: 6px; +} +.datepicker-dropdown:after { + content: ""; + display: inline-block; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid #ffffff; + position: absolute; + top: -6px; + left: 7px; +} +.datepicker > div { + display: none; +} +.datepicker.days div.datepicker-days { + display: block; +} +.datepicker.months div.datepicker-months { + display: block; +} +.datepicker.years div.datepicker-years { + display: block; +} +.datepicker table { + margin: 0; +} +.datepicker td, +.datepicker th { + text-align: center; + width: 20px; + height: 20px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + border: none; +} +.table-striped .datepicker table tr td, +.table-striped .datepicker table tr th { + background-color: transparent; +} +.datepicker table tr td.day:hover { + background: #eeeeee; + cursor: pointer; +} +.datepicker table tr td.old, +.datepicker table tr td.new { + color: #999999; +} +.datepicker table tr td.disabled, +.datepicker table tr td.disabled:hover { + background: none; + color: #999999; + cursor: default; +} +.datepicker table tr td.today, +.datepicker table tr td.today:hover, +.datepicker table tr td.today.disabled, +.datepicker table tr td.today.disabled:hover { + background-color: #fde19a; + background-image: -moz-linear-gradient(top, #fdd49a, #fdf59a); + background-image: -ms-linear-gradient(top, #fdd49a, #fdf59a); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a)); + background-image: -webkit-linear-gradient(top, #fdd49a, #fdf59a); + background-image: -o-linear-gradient(top, #fdd49a, #fdf59a); + background-image: linear-gradient(top, #fdd49a, #fdf59a); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0); + border-color: #fdf59a #fdf59a #fbed50; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} +.datepicker table tr td.today:hover, +.datepicker table tr td.today:hover:hover, +.datepicker table tr td.today.disabled:hover, +.datepicker table tr td.today.disabled:hover:hover, +.datepicker table tr td.today:active, +.datepicker table tr td.today:hover:active, +.datepicker table tr td.today.disabled:active, +.datepicker table tr td.today.disabled:hover:active, +.datepicker table tr td.today.active, +.datepicker table tr td.today:hover.active, +.datepicker table tr td.today.disabled.active, +.datepicker table tr td.today.disabled:hover.active, +.datepicker table tr td.today.disabled, +.datepicker table tr td.today:hover.disabled, +.datepicker table tr td.today.disabled.disabled, +.datepicker table tr td.today.disabled:hover.disabled, +.datepicker table tr td.today[disabled], +.datepicker table tr td.today:hover[disabled], +.datepicker table tr td.today.disabled[disabled], +.datepicker table tr td.today.disabled:hover[disabled] { + background-color: #fdf59a; +} +.datepicker table tr td.today:active, +.datepicker table tr td.today:hover:active, +.datepicker table tr td.today.disabled:active, +.datepicker table tr td.today.disabled:hover:active, +.datepicker table tr td.today.active, +.datepicker table tr td.today:hover.active, +.datepicker table tr td.today.disabled.active, +.datepicker table tr td.today.disabled:hover.active { + background-color: #fbf069 \9; +} +.datepicker table tr td.active, +.datepicker table tr td.active:hover, +.datepicker table tr td.active.disabled, +.datepicker table tr td.active.disabled:hover { + background-color: #006dcc; + background-image: -moz-linear-gradient(top, #0088cc, #0044cc); + background-image: -ms-linear-gradient(top, #0088cc, #0044cc); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); + background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); + background-image: -o-linear-gradient(top, #0088cc, #0044cc); + background-image: linear-gradient(top, #0088cc, #0044cc); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); + border-color: #0044cc #0044cc #002a80; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td.active:hover, +.datepicker table tr td.active:hover:hover, +.datepicker table tr td.active.disabled:hover, +.datepicker table tr td.active.disabled:hover:hover, +.datepicker table tr td.active:active, +.datepicker table tr td.active:hover:active, +.datepicker table tr td.active.disabled:active, +.datepicker table tr td.active.disabled:hover:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active:hover.active, +.datepicker table tr td.active.disabled.active, +.datepicker table tr td.active.disabled:hover.active, +.datepicker table tr td.active.disabled, +.datepicker table tr td.active:hover.disabled, +.datepicker table tr td.active.disabled.disabled, +.datepicker table tr td.active.disabled:hover.disabled, +.datepicker table tr td.active[disabled], +.datepicker table tr td.active:hover[disabled], +.datepicker table tr td.active.disabled[disabled], +.datepicker table tr td.active.disabled:hover[disabled] { + background-color: #0044cc; +} +.datepicker table tr td.active:active, +.datepicker table tr td.active:hover:active, +.datepicker table tr td.active.disabled:active, +.datepicker table tr td.active.disabled:hover:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active:hover.active, +.datepicker table tr td.active.disabled.active, +.datepicker table tr td.active.disabled:hover.active { + background-color: #003399 \9; +} +.datepicker table tr td span { + display: block; + width: 23%; + height: 54px; + line-height: 54px; + float: left; + margin: 1%; + cursor: pointer; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.datepicker table tr td span:hover { + background: #eeeeee; +} +.datepicker table tr td span.disabled, +.datepicker table tr td span.disabled:hover { + background: none; + color: #999999; + cursor: default; +} +.datepicker table tr td span.active, +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active.disabled:hover { + background-color: #006dcc; + background-image: -moz-linear-gradient(top, #0088cc, #0044cc); + background-image: -ms-linear-gradient(top, #0088cc, #0044cc); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); + background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); + background-image: -o-linear-gradient(top, #0088cc, #0044cc); + background-image: linear-gradient(top, #0088cc, #0044cc); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); + border-color: #0044cc #0044cc #002a80; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active:hover:hover, +.datepicker table tr td span.active.disabled:hover, +.datepicker table tr td span.active.disabled:hover:hover, +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active:hover.disabled, +.datepicker table tr td span.active.disabled.disabled, +.datepicker table tr td span.active.disabled:hover.disabled, +.datepicker table tr td span.active[disabled], +.datepicker table tr td span.active:hover[disabled], +.datepicker table tr td span.active.disabled[disabled], +.datepicker table tr td span.active.disabled:hover[disabled] { + background-color: #0044cc; +} +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active { + background-color: #003399 \9; +} +.datepicker table tr td span.old { + color: #999999; +} +.datepicker th.switch { + width: 145px; +} +.datepicker thead tr:first-child th, +.datepicker tfoot tr:first-child th { + cursor: pointer; +} +.datepicker thead tr:first-child th:hover, +.datepicker tfoot tr:first-child th:hover { + background: #eeeeee; +} +.input-append.date .add-on i, +.input-prepend.date .add-on i { + display: block; + cursor: pointer; + width: 16px; + height: 16px; +} diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.bg.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.bg.js new file mode 100644 index 0000000000..1c30b8258f --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.bg.js @@ -0,0 +1,49 @@ +/** + * Bulgarian translation for bootstrap-datepicker + * Apostol Apostolov + */ +(function ($) { + $.fn.datepicker.dates.bg = { + days: [ + "Неделя", + "Понеделник", + "Вторник", + "Сряда", + "Четвъртък", + "Петък", + "Събота", + "Неделя", + ], + daysShort: ["Нед", "Пон", "Вто", "Сря", "Чет", "Пет", "Съб", "Нед"], + daysMin: ["Н", "П", "В", "С", "Ч", "П", "С", "Н"], + months: [ + "Януари", + "Февруари", + "Март", + "Април", + "Май", + "Юни", + "Юли", + "Август", + "Септември", + "Октомври", + "Ноември", + "Декември", + ], + monthsShort: [ + "Ян", + "Фев", + "Мар", + "Апр", + "Май", + "Юни", + "Юли", + "Авг", + "Сеп", + "Окт", + "Ное", + "Дек", + ], + today: "днес", + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.ca.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.ca.js new file mode 100644 index 0000000000..2597513200 --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.ca.js @@ -0,0 +1,49 @@ +/** + * Catalan translation for bootstrap-datepicker + * J. Garcia + */ +(function ($) { + $.fn.datepicker.dates.ca = { + days: [ + "Diumenge", + "Dilluns", + "Dimarts", + "Dimecres", + "Dijous", + "Divendres", + "Dissabte", + "Diumenge", + ], + daysShort: ["Diu", "Dil", "Dmt", "Dmc", "Dij", "Div", "Dis", "Diu"], + daysMin: ["dg", "dl", "dt", "dc", "dj", "dv", "ds", "dg"], + months: [ + "Gener", + "Febrer", + "Març", + "Abril", + "Maig", + "Juny", + "Juliol", + "Agost", + "Setembre", + "Octubre", + "Novembre", + "Desembre", + ], + monthsShort: [ + "Gen", + "Feb", + "Mar", + "Abr", + "Mai", + "Jun", + "Jul", + "Ago", + "Set", + "Oct", + "Nov", + "Des", + ], + today: "Avui", + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.cs.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.cs.js new file mode 100644 index 0000000000..70c96475c4 --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.cs.js @@ -0,0 +1,50 @@ +/** + * Czech translation for bootstrap-datepicker + * Matěj Koubík + * Fixes by Michal Remiš + */ +(function ($) { + $.fn.datepicker.dates.cs = { + days: [ + "Neděle", + "Pondělí", + "Úterý", + "Středa", + "Čtvrtek", + "Pátek", + "Sobota", + "Neděle", + ], + daysShort: ["Ned", "Pon", "Úte", "Stř", "Čtv", "Pát", "Sob", "Ned"], + daysMin: ["Ne", "Po", "Út", "St", "Čt", "Pá", "So", "Ne"], + months: [ + "Leden", + "Únor", + "Březen", + "Duben", + "Květen", + "Červen", + "Červenec", + "Srpen", + "Září", + "Říjen", + "Listopad", + "Prosinec", + ], + monthsShort: [ + "Led", + "Úno", + "Bře", + "Dub", + "Kvě", + "Čer", + "Čnc", + "Srp", + "Zář", + "Říj", + "Lis", + "Pro", + ], + today: "Dnes", + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.da.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.da.js new file mode 100644 index 0000000000..90d1338bf7 --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.da.js @@ -0,0 +1,49 @@ +/** + * Danish translation for bootstrap-datepicker + * Christian Pedersen + */ +(function ($) { + $.fn.datepicker.dates.da = { + days: [ + "Søndag", + "Mandag", + "Tirsdag", + "Onsdag", + "Torsdag", + "Fredag", + "Lørdag", + "Søndag", + ], + daysShort: ["Søn", "Man", "Tir", "Ons", "Tor", "Fre", "Lør", "Søn"], + daysMin: ["Sø", "Ma", "Ti", "On", "To", "Fr", "Lø", "Sø"], + months: [ + "Januar", + "Februar", + "Marts", + "April", + "Maj", + "Juni", + "Juli", + "August", + "September", + "Oktober", + "November", + "December", + ], + monthsShort: [ + "Jan", + "Feb", + "Mar", + "Apr", + "Maj", + "Jun", + "Jul", + "Aug", + "Sep", + "Okt", + "Nov", + "Dec", + ], + today: "I Dag", + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.de.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.de.js new file mode 100644 index 0000000000..d7ac75124c --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.de.js @@ -0,0 +1,50 @@ +/** + * German translation for bootstrap-datepicker + * Sam Zurcher + */ +(function ($) { + $.fn.datepicker.dates.de = { + days: [ + "Sonntag", + "Montag", + "Dienstag", + "Mittwoch", + "Donnerstag", + "Freitag", + "Samstag", + "Sonntag", + ], + daysShort: ["Son", "Mon", "Die", "Mit", "Don", "Fre", "Sam", "Son"], + daysMin: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"], + months: [ + "Januar", + "Februar", + "März", + "April", + "Mai", + "Juni", + "Juli", + "August", + "September", + "Oktober", + "November", + "Dezember", + ], + monthsShort: [ + "Jan", + "Feb", + "Mär", + "Apr", + "Mai", + "Jun", + "Jul", + "Aug", + "Sep", + "Okt", + "Nov", + "Dez", + ], + today: "Heute", + weekStart: 1, + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.el.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.el.js new file mode 100644 index 0000000000..9f0e3e844d --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.el.js @@ -0,0 +1,48 @@ +/** + * Greek translation for bootstrap-datepicker + */ +(function ($) { + $.fn.datepicker.dates.el = { + days: [ + "Κυριακή", + "Δευτέρα", + "Τρίτη", + "Τετάρτη", + "Πέμπτη", + "Παρασκευή", + "Σάββατο", + "Κυριακή", + ], + daysShort: ["Κυρ", "Δευ", "Τρι", "Τετ", "Πεμ", "Παρ", "Σαβ", "Κυρ"], + daysMin: ["Κυ", "Δε", "Τρ", "Τε", "Πε", "Πα", "Σα", "Κυ"], + months: [ + "Ιανουάριος", + "Φεβρουάριος", + "Μάρτιος", + "Απρίλιος", + "Μάιος", + "Ιούνιος", + "Ιούλιος", + "Αύγουστος", + "Σεπτέμβριος", + "Οκτώβριος", + "Νοέμβριος", + "Δεκέμβριος", + ], + monthsShort: [ + "Ιαν", + "Φεβ", + "Μαρ", + "Απρ", + "Μάι", + "Ιουν", + "Ιουλ", + "Αυγ", + "Σεπ", + "Οκτ", + "Νοε", + "Δεκ", + ], + today: "Σήμερα", + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.es.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.es.js new file mode 100644 index 0000000000..eda5d80fe7 --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.es.js @@ -0,0 +1,49 @@ +/** + * Spanish translation for bootstrap-datepicker + * Bruno Bonamin + */ +(function ($) { + $.fn.datepicker.dates.es = { + days: [ + "Domingo", + "Lunes", + "Martes", + "Miércoles", + "Jueves", + "Viernes", + "Sábado", + "Domingo", + ], + daysShort: ["Dom", "Lun", "Mar", "Mié", "Jue", "Vie", "Sáb", "Dom"], + daysMin: ["Do", "Lu", "Ma", "Mi", "Ju", "Vi", "Sa", "Do"], + months: [ + "Enero", + "Febrero", + "Marzo", + "Abril", + "Mayo", + "Junio", + "Julio", + "Agosto", + "Septiembre", + "Octubre", + "Noviembre", + "Diciembre", + ], + monthsShort: [ + "Ene", + "Feb", + "Mar", + "Abr", + "May", + "Jun", + "Jul", + "Ago", + "Sep", + "Oct", + "Nov", + "Dic", + ], + today: "Hoy", + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.fi.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.fi.js new file mode 100644 index 0000000000..5c8e7e96a5 --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.fi.js @@ -0,0 +1,49 @@ +/** + * Finnish translation for bootstrap-datepicker + * Jaakko Salonen + */ +(function ($) { + $.fn.datepicker.dates.fi = { + days: [ + "sunnuntai", + "maanantai", + "tiistai", + "keskiviikko", + "torstai", + "perjantai", + "lauantai", + "sunnuntai", + ], + daysShort: ["sun", "maa", "tii", "kes", "tor", "per", "lau", "sun"], + daysMin: ["su", "ma", "ti", "ke", "to", "pe", "la", "su"], + months: [ + "tammikuu", + "helmikuu", + "maaliskuu", + "huhtikuu", + "toukokuu", + "kesäkuu", + "heinäkuu", + "elokuu", + "syyskuu", + "lokakuu", + "marraskuu", + "joulukuu", + ], + monthsShort: [ + "tam", + "hel", + "maa", + "huh", + "tou", + "kes", + "hei", + "elo", + "syy", + "lok", + "mar", + "jou", + ], + today: "tänään", + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.fr.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.fr.js new file mode 100644 index 0000000000..a604cbd265 --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.fr.js @@ -0,0 +1,50 @@ +/** + * French translation for bootstrap-datepicker + * Nico Mollet + */ +(function ($) { + $.fn.datepicker.dates.fr = { + days: [ + "Dimanche", + "Lundi", + "Mardi", + "Mercredi", + "Jeudi", + "Vendredi", + "Samedi", + "Dimanche", + ], + daysShort: ["Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"], + daysMin: ["D", "L", "Ma", "Me", "J", "V", "S", "D"], + months: [ + "Janvier", + "Février", + "Mars", + "Avril", + "Mai", + "Juin", + "Juillet", + "Août", + "Septembre", + "Octobre", + "Novembre", + "Décembre", + ], + monthsShort: [ + "Jan", + "Fev", + "Mar", + "Avr", + "Mai", + "Jui", + "Jul", + "Aou", + "Sep", + "Oct", + "Nov", + "Dec", + ], + today: "Aujourd'hui", + weekStart: 1, + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.he.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.he.js new file mode 100644 index 0000000000..826301f7ad --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.he.js @@ -0,0 +1,41 @@ +/** + * Hebrew translation for bootstrap-datepicker + * Sagie Maoz + */ +(function ($) { + $.fn.datepicker.dates.he = { + days: ["ראשון", "שני", "שלישי", "רביעי", "חמישי", "שישי", "שבת", "ראשון"], + daysShort: ["א", "ב", "ג", "ד", "ה", "ו", "ש", "א"], + daysMin: ["א", "ב", "ג", "ד", "ה", "ו", "ש", "א"], + months: [ + "ינואר", + "פברואר", + "מרץ", + "אפריל", + "מאי", + "יוני", + "יולי", + "אוגוסט", + "ספטמבר", + "אוקטובר", + "נובמבר", + "דצמבר", + ], + monthsShort: [ + "ינו", + "פבר", + "מרץ", + "אפר", + "מאי", + "יונ", + "יול", + "אוג", + "ספט", + "אוק", + "נוב", + "דצמ", + ], + today: "היום", + rtl: true, + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.hr.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.hr.js new file mode 100644 index 0000000000..1e0b2efa81 --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.hr.js @@ -0,0 +1,48 @@ +/** + * Croatian localisation + */ +(function ($) { + $.fn.datepicker.dates.hr = { + days: [ + "Nedjelja", + "Ponedjelja", + "Utorak", + "Srijeda", + "Četrtak", + "Petak", + "Subota", + "Nedjelja", + ], + daysShort: ["Ned", "Pon", "Uto", "Srr", "Čet", "Pet", "Sub", "Ned"], + daysMin: ["Ne", "Po", "Ut", "Sr", "Če", "Pe", "Su", "Ne"], + months: [ + "Siječanj", + "Veljača", + "Ožujak", + "Travanj", + "Svibanj", + "Lipanj", + "Srpanj", + "Kolovoz", + "Rujan", + "Listopad", + "Studeni", + "Prosinac", + ], + monthsShort: [ + "Sije", + "Velj", + "Ožu", + "Tra", + "Svi", + "Lip", + "Jul", + "Kol", + "Ruj", + "Lis", + "Stu", + "Pro", + ], + today: "Danas", + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.id.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.id.js new file mode 100644 index 0000000000..e8f0e5fd26 --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.id.js @@ -0,0 +1,48 @@ +/** + * Bahasa translation for bootstrap-datepicker + * Azwar Akbar + */ +(function ($) { + $.fn.datepicker.dates.id = { + days: [ + "Minggu", + "Senin", + "Selasa", + "Rabu", + "Kamis", + "Jumat", + "Sabtu", + "Minggu", + ], + daysShort: ["Mgu", "Sen", "Sel", "Rab", "Kam", "Jum", "Sab", "Mgu"], + daysMin: ["Mg", "Sn", "Sl", "Ra", "Ka", "Ju", "Sa", "Mg"], + months: [ + "Januari", + "Februari", + "Maret", + "April", + "Mei", + "Juni", + "Juli", + "Agustus", + "September", + "Oktober", + "November", + "Desember", + ], + monthsShort: [ + "Jan", + "Feb", + "Mar", + "Apr", + "Mei", + "Jun", + "Jul", + "Ags", + "Sep", + "Okt", + "Nov", + "Des", + ], + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.is.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.is.js new file mode 100644 index 0000000000..60b5447dd0 --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.is.js @@ -0,0 +1,49 @@ +/** + * Icelandic translation for bootstrap-datepicker + * Hinrik Örn Sigurðsson + */ +(function ($) { + $.fn.datepicker.dates.is = { + days: [ + "Sunnudagur", + "Mánudagur", + "Þriðjudagur", + "Miðvikudagur", + "Fimmtudagur", + "Föstudagur", + "Laugardagur", + "Sunnudagur", + ], + daysShort: ["Sun", "Mán", "Þri", "Mið", "Fim", "Fös", "Lau", "Sun"], + daysMin: ["Su", "Má", "Þr", "Mi", "Fi", "Fö", "La", "Su"], + months: [ + "Janúar", + "Febrúar", + "Mars", + "Apríl", + "Maí", + "Júní", + "Júlí", + "Ágúst", + "September", + "Október", + "Nóvember", + "Desember", + ], + monthsShort: [ + "Jan", + "Feb", + "Mar", + "Apr", + "Maí", + "Jún", + "Júl", + "Ágú", + "Sep", + "Okt", + "Nóv", + "Des", + ], + today: "Í Dag", + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.it.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.it.js new file mode 100644 index 0000000000..6f97d68560 --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.it.js @@ -0,0 +1,49 @@ +/** + * Italian translation for bootstrap-datepicker + * Enrico Rubboli + */ +(function ($) { + $.fn.datepicker.dates.it = { + days: [ + "Domenica", + "Lunedi", + "Martedi", + "Mercoledi", + "Giovedi", + "Venerdi", + "Sabato", + "Domenica", + ], + daysShort: ["Dom", "Lun", "Mar", "Mer", "Gio", "Ven", "Sab", "Dom"], + daysMin: ["Do", "Lu", "Ma", "Me", "Gi", "Ve", "Sa", "Do"], + months: [ + "Gennaio", + "Febbraio", + "Marzo", + "Aprile", + "Maggio", + "Giugno", + "Luglio", + "Agosto", + "Settembre", + "Ottobre", + "Novembre", + "Dicembre", + ], + monthsShort: [ + "Gen", + "Feb", + "Mar", + "Apr", + "Mag", + "Giu", + "Lug", + "Ago", + "Set", + "Ott", + "Nov", + "Dic", + ], + today: "Oggi", + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.ja.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.ja.js new file mode 100644 index 0000000000..28661ef627 --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.ja.js @@ -0,0 +1,39 @@ +/** + * Japanese translation for bootstrap-datepicker + * Norio Suzuki + */ +(function ($) { + $.fn.datepicker.dates.ja = { + days: ["日曜", "月曜", "火曜", "水曜", "木曜", "金曜", "土曜", "日曜"], + daysShort: ["日", "月", "火", "水", "木", "金", "土", "日"], + daysMin: ["日", "月", "火", "水", "木", "金", "土", "日"], + months: [ + "1月", + "2月", + "3月", + "4月", + "5月", + "6月", + "7月", + "8月", + "9月", + "10月", + "11月", + "12月", + ], + monthsShort: [ + "1月", + "2月", + "3月", + "4月", + "5月", + "6月", + "7月", + "8月", + "9月", + "10月", + "11月", + "12月", + ], + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.kr.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.kr.js new file mode 100644 index 0000000000..50e7d119e0 --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.kr.js @@ -0,0 +1,48 @@ +/** + * Korean translation for bootstrap-datepicker + * Gu Youn + */ +(function ($) { + $.fn.datepicker.dates.kr = { + days: [ + "일요일", + "월요일", + "화요일", + "수요일", + "목요일", + "금요일", + "토요일", + "일요일", + ], + daysShort: ["일", "월", "화", "수", "목", "금", "토", "일"], + daysMin: ["일", "월", "화", "수", "목", "금", "토", "일"], + months: [ + "1월", + "2월", + "3월", + "4월", + "5월", + "6월", + "7월", + "8월", + "9월", + "10월", + "11월", + "12월", + ], + monthsShort: [ + "1월", + "2월", + "3월", + "4월", + "5월", + "6월", + "7월", + "8월", + "9월", + "10월", + "11월", + "12월", + ], + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.lt.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.lt.js new file mode 100644 index 0000000000..63c65c9a4e --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.lt.js @@ -0,0 +1,51 @@ +/** + * Lithuanian translation for bootstrap-datepicker + * Šarūnas Gliebus + */ + +(function ($) { + $.fn.datepicker.dates.lt = { + days: [ + "Sekmadienis", + "Pirmadienis", + "Antradienis", + "Trečiadienis", + "Ketvirtadienis", + "Penktadienis", + "Šeštadienis", + "Sekmadienis", + ], + daysShort: ["S", "Pr", "A", "T", "K", "Pn", "Š", "S"], + daysMin: ["Sk", "Pr", "An", "Tr", "Ke", "Pn", "Št", "Sk"], + months: [ + "Sausis", + "Vasaris", + "Kovas", + "Balandis", + "Gegužė", + "Birželis", + "Liepa", + "Rugpjūtis", + "Rugsėjis", + "Spalis", + "Lapkritis", + "Gruodis", + ], + monthsShort: [ + "Sau", + "Vas", + "Kov", + "Bal", + "Geg", + "Bir", + "Lie", + "Rugp", + "Rugs", + "Spa", + "Lap", + "Gru", + ], + today: "Šiandien", + weekStart: 1, + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.lv.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.lv.js new file mode 100644 index 0000000000..464680fe66 --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.lv.js @@ -0,0 +1,51 @@ +/** + * Latvian translation for bootstrap-datepicker + * Artis Avotins + */ + +(function ($) { + $.fn.datepicker.dates.lv = { + days: [ + "Svētdiena", + "Pirmdiena", + "Otrdiena", + "Trešdiena", + "Ceturtdiena", + "Piektdiena", + "Sestdiena", + "Svētdiena", + ], + daysShort: ["Sv", "P", "O", "T", "C", "Pk", "S", "Sv"], + daysMin: ["Sv", "Pr", "Ot", "Tr", "Ce", "Pk", "St", "Sv"], + months: [ + "Janvāris", + "Februāris", + "Marts", + "Aprīlis", + "Maijs", + "Jūnijs", + "Jūlijs", + "Augusts", + "Septembris", + "Oktobris", + "Novembris", + "Decembris", + ], + monthsShort: [ + "Jan", + "Feb", + "Mar", + "Apr", + "Mai", + "Jūn", + "Jūl", + "Aug", + "Sep", + "Okt", + "Nov", + "Dec.", + ], + today: "Šodien", + weekStart: 1, + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.ms.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.ms.js new file mode 100644 index 0000000000..4b5ba55247 --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.ms.js @@ -0,0 +1,40 @@ +/** + * Malay translation for bootstrap-datepicker + * Ateman Faiz + */ +(function ($) { + $.fn.datepicker.dates.ms = { + days: ["Ahad", "Isnin", "Selasa", "Rabu", "Khamis", "Jumaat", "Sabtu", "Ahad"], + daysShort: ["Aha", "Isn", "Sel", "Rab", "Kha", "Jum", "Sab", "Aha"], + daysMin: ["Ah", "Is", "Se", "Ra", "Kh", "Ju", "Sa", "Ah"], + months: [ + "Januari", + "Februari", + "Mac", + "April", + "Mei", + "Jun", + "Julai", + "Ogos", + "September", + "Oktober", + "November", + "Disember", + ], + monthsShort: [ + "Jan", + "Feb", + "Mar", + "Apr", + "Mei", + "Jun", + "Jul", + "Ogo", + "Sep", + "Okt", + "Nov", + "Dis", + ], + today: "Hari Ini", + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.nb.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.nb.js new file mode 100644 index 0000000000..146fb838a0 --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.nb.js @@ -0,0 +1,49 @@ +/** + * Norwegian (bokmål) translation for bootstrap-datepicker + * Fredrik Sundmyhr + */ +(function ($) { + $.fn.datepicker.dates.nb = { + days: [ + "Søndag", + "Mandag", + "Tirsdag", + "Onsdag", + "Torsdag", + "Fredag", + "Lørdag", + "Søndag", + ], + daysShort: ["Søn", "Man", "Tir", "Ons", "Tor", "Fre", "Lør", "Søn"], + daysMin: ["Sø", "Ma", "Ti", "On", "To", "Fr", "Lø", "Sø"], + months: [ + "Januar", + "Februar", + "Mars", + "April", + "Mai", + "Juni", + "Juli", + "August", + "September", + "Oktober", + "November", + "Desember", + ], + monthsShort: [ + "Jan", + "Feb", + "Mar", + "Apr", + "Mai", + "Jun", + "Jul", + "Aug", + "Sep", + "Okt", + "Nov", + "Des", + ], + today: "I Dag", + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.nl.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.nl.js new file mode 100644 index 0000000000..4151a57a8b --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.nl.js @@ -0,0 +1,49 @@ +/** + * Dutch translation for bootstrap-datepicker + * Reinier Goltstein + */ +(function ($) { + $.fn.datepicker.dates.nl = { + days: [ + "Zondag", + "Maandag", + "Dinsdag", + "Woensdag", + "Donderdag", + "Vrijdag", + "Zaterdag", + "Zondag", + ], + daysShort: ["Zo", "Ma", "Di", "Wo", "Do", "Vr", "Za", "Zo"], + daysMin: ["Zo", "Ma", "Di", "Wo", "Do", "Vr", "Za", "Zo"], + months: [ + "Januari", + "Februari", + "Maart", + "April", + "Mei", + "Juni", + "Juli", + "Augustus", + "September", + "Oktober", + "November", + "December", + ], + monthsShort: [ + "Jan", + "Feb", + "Mrt", + "Apr", + "Mei", + "Jun", + "Jul", + "Aug", + "Sep", + "Okt", + "Nov", + "Dec", + ], + today: "Vandaag", + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.pl.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.pl.js new file mode 100644 index 0000000000..4bce1fd36d --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.pl.js @@ -0,0 +1,50 @@ +/** + * Polish translation for bootstrap-datepicker + * Robert + */ +(function ($) { + $.fn.datepicker.dates.pl = { + days: [ + "Niedziela", + "Poniedziałek", + "Wtorek", + "Środa", + "Czwartek", + "Piątek", + "Sobota", + "Niedziela", + ], + daysShort: ["Nie", "Pn", "Wt", "Śr", "Czw", "Pt", "So", "Nie"], + daysMin: ["N", "Pn", "Wt", "Śr", "Cz", "Pt", "So", "N"], + months: [ + "Styczeń", + "Luty", + "Marzec", + "Kwiecień", + "Maj", + "Czerwiec", + "Lipiec", + "Sierpień", + "Wrzesień", + "Październik", + "Listopad", + "Grudzień", + ], + monthsShort: [ + "Sty", + "Lu", + "Mar", + "Kw", + "Maj", + "Cze", + "Lip", + "Sie", + "Wrz", + "Pa", + "Lis", + "Gru", + ], + today: "Dzisiaj", + weekStart: 1, + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.pt-BR.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.pt-BR.js new file mode 100644 index 0000000000..85fccd2979 --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.pt-BR.js @@ -0,0 +1,49 @@ +/** + * Brazilian translation for bootstrap-datepicker + * Cauan Cabral + */ +(function ($) { + $.fn.datepicker.dates["pt-BR"] = { + days: [ + "Domingo", + "Segunda", + "Terça", + "Quarta", + "Quinta", + "Sexta", + "Sábado", + "Domingo", + ], + daysShort: ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb", "Dom"], + daysMin: ["Do", "Se", "Te", "Qu", "Qu", "Se", "Sa", "Do"], + months: [ + "Janeiro", + "Fevereiro", + "Março", + "Abril", + "Maio", + "Junho", + "Julho", + "Agosto", + "Setembro", + "Outubro", + "Novembro", + "Dezembro", + ], + monthsShort: [ + "Jan", + "Fev", + "Mar", + "Abr", + "Mai", + "Jun", + "Jul", + "Ago", + "Set", + "Out", + "Nov", + "Dez", + ], + today: "Hoje", + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.pt.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.pt.js new file mode 100644 index 0000000000..0e33f95641 --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.pt.js @@ -0,0 +1,49 @@ +/** + * Portuguese translation for bootstrap-datepicker + * Original code: Cauan Cabral + * Tiago Melo + */ +(function ($) { + $.fn.datepicker.dates.pt = { + days: [ + "Domingo", + "Segunda", + "Terça", + "Quarta", + "Quinta", + "Sexta", + "Sábado", + "Domingo", + ], + daysShort: ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb", "Dom"], + daysMin: ["Do", "Se", "Te", "Qu", "Qu", "Se", "Sa", "Do"], + months: [ + "Janeiro", + "Fevereiro", + "Março", + "Abril", + "Maio", + "Junho", + "Julho", + "Agosto", + "Setembro", + "Outubro", + "Novembro", + "Dezembro", + ], + monthsShort: [ + "Jan", + "Fev", + "Mar", + "Abr", + "Mai", + "Jun", + "Jul", + "Ago", + "Set", + "Out", + "Nov", + "Dez", + ], + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.ro.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.ro.js new file mode 100644 index 0000000000..eefb0f0675 --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.ro.js @@ -0,0 +1,50 @@ +/** + * Romanian translation for bootstrap-datepicker + * Cristian Vasile + */ +(function ($) { + $.fn.datepicker.dates.ro = { + days: [ + "Duminică", + "Luni", + "Marţi", + "Miercuri", + "Joi", + "Vineri", + "Sâmbătă", + "Duminică", + ], + daysShort: ["Dum", "Lun", "Mar", "Mie", "Joi", "Vin", "Sâm", "Dum"], + daysMin: ["Du", "Lu", "Ma", "Mi", "Jo", "Vi", "Sâ", "Du"], + months: [ + "Ianuarie", + "Februarie", + "Martie", + "Aprilie", + "Mai", + "Iunie", + "Iulie", + "August", + "Septembrie", + "Octombrie", + "Noiembrie", + "Decembrie", + ], + monthsShort: [ + "Ian", + "Feb", + "Mar", + "Apr", + "Mai", + "Iun", + "Iul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ], + today: "Astăzi", + weekStart: 1, + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.rs-latin.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.rs-latin.js new file mode 100644 index 0000000000..6973edc895 --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.rs-latin.js @@ -0,0 +1,49 @@ +/** + * Serbian latin translation for bootstrap-datepicker + * Bojan Milosavlević + */ +(function ($) { + $.fn.datepicker.dates.rs = { + days: [ + "Nedelja", + "Ponedeljak", + "Utorak", + "Sreda", + "Četvrtak", + "Petak", + "Subota", + "Nedelja", + ], + daysShort: ["Ned", "Pon", "Uto", "Sre", "Čet", "Pet", "Sub", "Ned"], + daysMin: ["N", "Po", "U", "Sr", "Č", "Pe", "Su", "N"], + months: [ + "Januar", + "Februar", + "Mart", + "April", + "Maj", + "Jun", + "Jul", + "Avgust", + "Septembar", + "Oktobar", + "Novembar", + "Decembar", + ], + monthsShort: [ + "Jan", + "Feb", + "Mar", + "Apr", + "Maj", + "Jun", + "Jul", + "Avg", + "Sep", + "Okt", + "Nov", + "Dec", + ], + today: "Danas", + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.rs.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.rs.js new file mode 100644 index 0000000000..137fadb40b --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.rs.js @@ -0,0 +1,49 @@ +/** + * Serbian cyrillic translation for bootstrap-datepicker + * Bojan Milosavlević + */ +(function ($) { + $.fn.datepicker.dates.rs = { + days: [ + "Недеља", + "Понедељак", + "Уторак", + "Среда", + "Четвртак", + "Петак", + "Субота", + "Недеља", + ], + daysShort: ["Нед", "Пон", "Уто", "Сре", "Чет", "Пет", "Суб", "Нед"], + daysMin: ["Н", "По", "У", "Ср", "Ч", "Пе", "Су", "Н"], + months: [ + "Јануар", + "Фебруар", + "Март", + "Април", + "Мај", + "Јун", + "Јул", + "Август", + "Септембар", + "Октобар", + "Новембар", + "Децембар", + ], + monthsShort: [ + "Јан", + "Феб", + "Мар", + "Апр", + "Мај", + "Јун", + "Јул", + "Авг", + "Сеп", + "Окт", + "Нов", + "Дец", + ], + today: "Данас", + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.ru.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.ru.js new file mode 100644 index 0000000000..52e0864e62 --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.ru.js @@ -0,0 +1,49 @@ +/** + * Russian translation for bootstrap-datepicker + * Victor Taranenko + */ +(function ($) { + $.fn.datepicker.dates.ru = { + days: [ + "Воскресенье", + "Понедельник", + "Вторник", + "Среда", + "Четверг", + "Пятница", + "Суббота", + "Воскресенье", + ], + daysShort: ["Вск", "Пнд", "Втр", "Срд", "Чтв", "Птн", "Суб", "Вск"], + daysMin: ["Вс", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"], + months: [ + "Январь", + "Февраль", + "Март", + "Апрель", + "Май", + "Июнь", + "Июль", + "Август", + "Сентябрь", + "Октябрь", + "Ноябрь", + "Декабрь", + ], + monthsShort: [ + "Янв", + "Фев", + "Мар", + "Апр", + "Май", + "Июн", + "Июл", + "Авг", + "Сен", + "Окт", + "Ноя", + "Дек", + ], + today: "Сегодня", + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.sk.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.sk.js new file mode 100644 index 0000000000..c3103c6def --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.sk.js @@ -0,0 +1,50 @@ +/** + * Slovak translation for bootstrap-datepicker + * Marek Lichtner + * Fixes by Michal Remiš + */ +(function ($) { + $.fn.datepicker.dates.sk = { + days: [ + "Nedeľa", + "Pondelok", + "Utorok", + "Streda", + "Štvrtok", + "Piatok", + "Sobota", + "Nedeľa", + ], + daysShort: ["Ned", "Pon", "Uto", "Str", "Štv", "Pia", "Sob", "Ned"], + daysMin: ["Ne", "Po", "Ut", "St", "Št", "Pia", "So", "Ne"], + months: [ + "Január", + "Február", + "Marec", + "Apríl", + "Máj", + "Jún", + "Júl", + "August", + "September", + "Október", + "November", + "December", + ], + monthsShort: [ + "Jan", + "Feb", + "Mar", + "Apr", + "Máj", + "Jún", + "Júl", + "Aug", + "Sep", + "Okt", + "Nov", + "Dec", + ], + today: "Dnes", + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.sl.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.sl.js new file mode 100644 index 0000000000..7860379afc --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.sl.js @@ -0,0 +1,49 @@ +/** + * Slovene translation for bootstrap-datepicker + * Gregor Rudolf + */ +(function ($) { + $.fn.datepicker.dates.sl = { + days: [ + "Nedelja", + "Ponedeljek", + "Torek", + "Sreda", + "Četrtek", + "Petek", + "Sobota", + "Nedelja", + ], + daysShort: ["Ned", "Pon", "Tor", "Sre", "Čet", "Pet", "Sob", "Ned"], + daysMin: ["Ne", "Po", "To", "Sr", "Če", "Pe", "So", "Ne"], + months: [ + "Januar", + "Februar", + "Marec", + "April", + "Maj", + "Junij", + "Julij", + "Avgust", + "September", + "Oktober", + "November", + "December", + ], + monthsShort: [ + "Jan", + "Feb", + "Mar", + "Apr", + "Maj", + "Jun", + "Jul", + "Avg", + "Sep", + "Okt", + "Nov", + "Dec", + ], + today: "Danes", + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.sv.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.sv.js new file mode 100644 index 0000000000..7fec76265f --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.sv.js @@ -0,0 +1,49 @@ +/** + * Swedish translation for bootstrap-datepicker + * Patrik Ragnarsson + */ +(function ($) { + $.fn.datepicker.dates.sv = { + days: [ + "Söndag", + "Måndag", + "Tisdag", + "Onsdag", + "Torsdag", + "Fredag", + "Lördag", + "Söndag", + ], + daysShort: ["Sön", "Mån", "Tis", "Ons", "Tor", "Fre", "Lör", "Sön"], + daysMin: ["Sö", "Må", "Ti", "On", "To", "Fr", "Lö", "Sö"], + months: [ + "Januari", + "Februari", + "Mars", + "April", + "Maj", + "Juni", + "Juli", + "Augusti", + "September", + "Oktober", + "November", + "December", + ], + monthsShort: [ + "Jan", + "Feb", + "Mar", + "Apr", + "Maj", + "Jun", + "Jul", + "Aug", + "Sep", + "Okt", + "Nov", + "Dec", + ], + today: "I Dag", + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.sw.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.sw.js new file mode 100644 index 0000000000..2b08970d1b --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.sw.js @@ -0,0 +1,50 @@ +/** + * Swahili translation for bootstrap-datepicker + * Edwin Mugendi + * Source: http://scriptsource.org/cms/scripts/page.php?item_id=entry_detail&uid=xnfaqyzcku + */ +(function ($) { + $.fn.datepicker.dates.sw = { + days: [ + "Jumapili", + "Jumatatu", + "Jumanne", + "Jumatano", + "Alhamisi", + "Ijumaa", + "Jumamosi", + "Jumapili", + ], + daysShort: ["J2", "J3", "J4", "J5", "Alh", "Ij", "J1", "J2"], + daysMin: ["2", "3", "4", "5", "A", "I", "1", "2"], + months: [ + "Januari", + "Februari", + "Machi", + "Aprili", + "Mei", + "Juni", + "Julai", + "Agosti", + "Septemba", + "Oktoba", + "Novemba", + "Desemba", + ], + monthsShort: [ + "Jan", + "Feb", + "Mac", + "Apr", + "Mei", + "Jun", + "Jul", + "Ago", + "Sep", + "Okt", + "Nov", + "Des", + ], + today: "Leo", + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.th.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.th.js new file mode 100644 index 0000000000..5aa40b78ac --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.th.js @@ -0,0 +1,49 @@ +/** + * Thai translation for bootstrap-datepicker + * Suchau Jiraprapot + */ +(function ($) { + $.fn.datepicker.dates.th = { + days: [ + "อาทิตย์", + "จันทร์", + "อังคาร", + "พุธ", + "พฤหัส", + "ศุกร์", + "เสาร์", + "อาทิตย์", + ], + daysShort: ["อา", "จ", "อ", "พ", "พฤ", "ศ", "ส", "อา"], + daysMin: ["อา", "จ", "อ", "พ", "พฤ", "ศ", "ส", "อา"], + months: [ + "มกราคม", + "กุมภาพันธ์", + "มีนาคม", + "เมษายน", + "พฤษภาคม", + "มิถุนายน", + "กรกฎาคม", + "สิงหาคม", + "กันยายน", + "ตุลาคม", + "พฤศจิกายน", + "ธันวาคม", + ], + monthsShort: [ + "ม.ค.", + "ก.พ.", + "มี.ค.", + "เม.ย.", + "พ.ค.", + "มิ.ย.", + "ก.ค.", + "ส.ค.", + "ก.ย.", + "ต.ค.", + "พ.ย.", + "ธ.ค.", + ], + today: "วันนี้", + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.tr.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.tr.js new file mode 100644 index 0000000000..ceabb07caa --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.tr.js @@ -0,0 +1,49 @@ +/** + * Turkish translation for bootstrap-datepicker + * Serkan Algur + */ +(function ($) { + $.fn.datepicker.dates.tr = { + days: [ + "Pazar", + "Pazartesi", + "Salı", + "Çarşamba", + "Perşembe", + "Cuma", + "Cumartesi", + "Pazar", + ], + daysShort: ["Pz", "Pzt", "Sal", "Çrş", "Prş", "Cu", "Cts", "Pz"], + daysMin: ["Pz", "Pzt", "Sa", "Çr", "Pr", "Cu", "Ct", "Pz"], + months: [ + "Ocak", + "Şubat", + "Mart", + "Nisan", + "Mayıs", + "Haziran", + "Temmuz", + "Ağustos", + "Eylül", + "Ekim", + "Kasım", + "Aralık", + ], + monthsShort: [ + "Oca", + "Şub", + "Mar", + "Nis", + "May", + "Haz", + "Tem", + "Ağu", + "Eyl", + "Eki", + "Kas", + "Ara", + ], + today: "Bugün", + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.uk.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.uk.js new file mode 100644 index 0000000000..2fef40c171 --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.uk.js @@ -0,0 +1,49 @@ +/** + * Ukrainian translation for bootstrap-datepicker + * Andrey Vityuk + */ +(function ($) { + $.fn.datepicker.dates.uk = { + days: [ + "Неділя", + "Понеділок", + "Вівторок", + "Середа", + "Четвер", + "П'ятниця", + "Субота", + "Неділя", + ], + daysShort: ["Нед", "Пнд", "Втр", "Срд", "Чтв", "Птн", "Суб", "Нед"], + daysMin: ["Нд", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Нд"], + months: [ + "Січень", + "Лютий", + "Березень", + "Квітень", + "Травень", + "Червень", + "Липень", + "Серпень", + "Вересень", + "Жовтень", + "Листопад", + "Грудень", + ], + monthsShort: [ + "Січ", + "Лют", + "Бер", + "Кві", + "Тра", + "Чер", + "Лип", + "Сер", + "Вер", + "Жов", + "Лис", + "Гру", + ], + today: "Сьогодні", + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.zh-CN.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.zh-CN.js new file mode 100644 index 0000000000..a9e6d6a5c1 --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.zh-CN.js @@ -0,0 +1,49 @@ +/** + * Simplified Chinese translation for bootstrap-datepicker + * Yuan Cheung + */ +(function ($) { + $.fn.datepicker.dates["zh-CN"] = { + days: [ + "星期日", + "星期一", + "星期二", + "星期三", + "星期四", + "星期五", + "星期六", + "星期日", + ], + daysShort: ["周日", "周一", "周二", "周三", "周四", "周五", "周六", "周日"], + daysMin: ["日", "一", "二", "三", "四", "五", "六", "日"], + months: [ + "一月", + "二月", + "三月", + "四月", + "五月", + "六月", + "七月", + "八月", + "九月", + "十月", + "十一月", + "十二月", + ], + monthsShort: [ + "一月", + "二月", + "三月", + "四月", + "五月", + "六月", + "七月", + "八月", + "九月", + "十月", + "十一月", + "十二月", + ], + today: "今日", + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.zh-TW.js b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.zh-TW.js new file mode 100644 index 0000000000..0acedd4f4e --- /dev/null +++ b/pms/static/src/js/widgets/datepicker/locales/bootstrap-datepicker.zh-TW.js @@ -0,0 +1,48 @@ +/** + * Traditional Chinese translation for bootstrap-datepicker + * Rung-Sheng Jang + */ +(function ($) { + $.fn.datepicker.dates["zh-TW"] = { + days: [ + "星期日", + "星期一", + "星期二", + "星期三", + "星期四", + "星期五", + "星期六", + "星期日", + ], + daysShort: ["周日", "周一", "周二", "周三", "周四", "周五", "周六", "周日"], + daysMin: ["日", "一", "二", "三", "四", "五", "六", "日"], + months: [ + "一月", + "二月", + "三月", + "四月", + "五月", + "六月", + "七月", + "八月", + "九月", + "十月", + "十一月", + "十二月", + ], + monthsShort: [ + "一月", + "二月", + "三月", + "四月", + "五月", + "六月", + "七月", + "八月", + "九月", + "十月", + "十一月", + "十二月", + ], + }; +})(jQuery); diff --git a/pms/static/src/js/widgets/switch_property_menu.js b/pms/static/src/js/widgets/switch_property_menu.js new file mode 100644 index 0000000000..ecb77c510e --- /dev/null +++ b/pms/static/src/js/widgets/switch_property_menu.js @@ -0,0 +1,166 @@ +odoo.define("web.SwitchPmsMenu", function (require) { + "use strict"; + + /** + * When Odoo is configured in multi-property mode, users should obviously be able + * to switch their interface from one property to the other. This is the purpose + * of this widget, by displaying a dropdown menu in the systray. + */ + + var config = require("web.config"); + var session = require("web.session"); + var SystrayMenu = require("web.SystrayMenu"); + var Widget = require("web.Widget"); + + var SwitchPmsMenu = Widget.extend({ + template: "SwitchPmsMenu", + events: { + "click .dropdown-item[data-menu] div.pms_log_into": + "_onSwitchPmsPropertyClick", + "keydown .dropdown-item[data-menu] div.pms_log_into": + "_onSwitchPmsPropertyClick", + "click .dropdown-item[data-menu] div.pms_toggle_property": + "_onTogglePmsPropertyClick", + "keydown .dropdown-item[data-menu] div.pms_toggle_property": + "_onTogglePmsPropertyClick", + }, + /** + * @override + */ + init: function () { + this._super.apply(this, arguments); + this.isMobile = config.device.isMobile; + this._onSwitchPmsPropertyClick = _.debounce( + this._onSwitchPmsPropertyClick, + 1500, + true + ); + }, + + /** + * @override + */ + willStart: function () { + var self = this; + this.allowed_pms_property_ids = String( + session.user_context.allowed_pms_property_ids + ) + .split(",") + .map(function (id) { + return parseInt(id, 10); + }); + this.user_pms_properties = + session.user_pms_properties.allowed_pms_properties; + this.user_pms_properties.sort(function (a, b) { + return a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0; + }); + this.current_pms_property = this.allowed_pms_property_ids[0]; + this.current_pms_property_name = _.find( + session.user_pms_properties.allowed_pms_properties, + function (pms_property) { + return pms_property[0] === self.current_pms_property; + } + )[1]; + return this._super.apply(this, arguments); + }, + + // -------------------------------------------------------------------------- + // Handlers + // -------------------------------------------------------------------------- + + /** + * @private + * @param {MouseEvent|KeyEvent} ev + */ + _onSwitchPmsPropertyClick: function (ev) { + if ( + ev.type === "keydown" && + ev.which !== $.ui.keyCode.ENTER && + ev.which !== $.ui.keyCode.SPACE + ) { + return; + } + ev.preventDefault(); + ev.stopPropagation(); + var dropdownItem = $(ev.currentTarget).parent(); + var dropdownMenu = dropdownItem.parent(); + var pms_propertyID = dropdownItem.data("pms_property-id"); + var allowed_pms_property_ids = this.allowed_pms_property_ids; + if (dropdownItem.find(".fa-square-o").length) { + // 1 enabled pms_property: Stay in single pms proeprty mode + if (this.allowed_pms_property_ids.length === 1) { + if (this.isMobile) { + dropdownMenu = dropdownMenu.parent(); + } + dropdownMenu + .find(".fa-check-square") + .removeClass("fa-check-square") + .addClass("fa-square-o"); + dropdownItem + .find(".fa-square-o") + .removeClass("fa-square-o") + .addClass("fa-check-square"); + allowed_pms_property_ids = [pms_propertyID]; + } else { + // Multi pms proeprty mode + allowed_pms_property_ids.push(pms_propertyID); + dropdownItem + .find(".fa-square-o") + .removeClass("fa-square-o") + .addClass("fa-check-square"); + } + } + $(ev.currentTarget).attr("aria-pressed", "true"); + session.setPmsProperties(pms_propertyID, allowed_pms_property_ids); + }, + + // -------------------------------------------------------------------------- + // Handlers + // -------------------------------------------------------------------------- + + /** + * @private + * @param {MouseEvent|KeyEvent} ev + */ + _onTogglePmsPropertyClick: function (ev) { + if ( + ev.type === "keydown" && + ev.which !== $.ui.keyCode.ENTER && + ev.which !== $.ui.keyCode.SPACE + ) { + return; + } + ev.preventDefault(); + ev.stopPropagation(); + var dropdownItem = $(ev.currentTarget).parent(); + var pms_propertyID = dropdownItem.data("pms_property-id"); + var allowed_pms_property_ids = this.allowed_pms_property_ids; + var current_pms_property_id = allowed_pms_property_ids[0]; + if (dropdownItem.find(".fa-square-o").length) { + allowed_pms_property_ids.push(pms_propertyID); + dropdownItem + .find(".fa-square-o") + .removeClass("fa-square-o") + .addClass("fa-check-square"); + $(ev.currentTarget).attr("aria-checked", "true"); + } else { + allowed_pms_property_ids.splice( + allowed_pms_property_ids.indexOf(pms_propertyID), + 1 + ); + dropdownItem + .find(".fa-check-square") + .addClass("fa-square-o") + .removeClass("fa-check-square"); + $(ev.currentTarget).attr("aria-checked", "false"); + } + session.setPmsProperties(current_pms_property_id, allowed_pms_property_ids); + }, + }); + + if (session.display_switch_pms_property_menu) { + SystrayMenu.Items.push(SwitchPmsMenu); + } + + return SwitchPmsMenu; +}); diff --git a/pms/static/src/xml/account_reconciliation.xml b/pms/static/src/xml/account_reconciliation.xml new file mode 100644 index 0000000000..97fb89d569 --- /dev/null +++ b/pms/static/src/xml/account_reconciliation.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/pms/static/src/xml/pms_base_templates.xml b/pms/static/src/xml/pms_base_templates.xml new file mode 100644 index 0000000000..b054fb56cb --- /dev/null +++ b/pms/static/src/xml/pms_base_templates.xml @@ -0,0 +1,76 @@ + diff --git a/pms/static/src/xml/reservation_group_button_views.xml b/pms/static/src/xml/reservation_group_button_views.xml new file mode 100644 index 0000000000..05e713adbc --- /dev/null +++ b/pms/static/src/xml/reservation_group_button_views.xml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/pms/templates/pms_email_template.xml b/pms/templates/pms_email_template.xml new file mode 100644 index 0000000000..df7de0ebd4 --- /dev/null +++ b/pms/templates/pms_email_template.xml @@ -0,0 +1,154 @@ + + + + + + + + + + Property: Reservation Confirmed + + ${('%s <%s>' % (object.pms_property_id.partner_id.name, object.pms_property_id.partner_id.email) or '')|safe} + ${(object.email or '')|safe} + ${(object.partner_id.id or '')} + ${object.lang} + + Your reservation ${object.name} has been confirmed by the property staff + + + qweb + + + diff --git a/pms/tests/__init__.py b/pms/tests/__init__.py new file mode 100644 index 0000000000..4d084fc6cb --- /dev/null +++ b/pms/tests/__init__.py @@ -0,0 +1,43 @@ +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2017 Solucións Aloxa S.L. +# Alexandre Díaz +# +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################## +from . import test_pms_reservation +from . import test_pms_pricelist +from . import test_pms_checkin_partner +from . import test_pms_sale_channel +from . import test_pms_folio +from . import test_pms_availability_plan_rules +from . import test_pms_room_type +from . import test_pms_room_type_class +from . import test_pms_board_service +from . import test_pms_wizard_massive_changes +from . import test_pms_booking_engine +from . import test_pms_res_users +from . import test_pms_room +from . import test_pms_folio_invoice +from . import test_pms_folio_sale_line +from . import test_pms_wizard_split_join_swap_reservation +from . import test_product_template +from . import test_pms_multiproperty +from . import test_shared_room + +# from . import test_automated_mails +from . import test_pms_service diff --git a/pms/tests/common.py b/pms/tests/common.py new file mode 100644 index 0000000000..866ed57faf --- /dev/null +++ b/pms/tests/common.py @@ -0,0 +1,38 @@ +from odoo.tests import common + + +class TestPms(common.SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.availability_plan1 = cls.env["pms.availability.plan"].create( + {"name": "Availability Plan 1"} + ) + cls.pricelist1 = cls.env["product.pricelist"].create( + { + "name": "Pricelist 1", + "availability_plan_id": cls.availability_plan1.id, + } + ) + cls.company1 = cls.env["res.company"].create( + { + "name": "Company 1", + } + ) + cls.pms_property1 = cls.env["pms.property"].create( + { + "name": "Property 1", + "company_id": cls.company1.id, + "default_pricelist_id": cls.pricelist1.id, + } + ) + cls.room_type_class1 = cls.env["pms.room.type.class"].create( + { + "name": "Room Type Class 1", + "default_code": "RTC1", + } + ) + for pricelist in cls.env["product.pricelist"].search([]): + if not pricelist.availability_plan_id: + pricelist.availability_plan_id = cls.availability_plan1.id + pricelist.is_pms_available = True diff --git a/pms/tests/test_automated_mails.py b/pms/tests/test_automated_mails.py new file mode 100644 index 0000000000..28b95d4dd8 --- /dev/null +++ b/pms/tests/test_automated_mails.py @@ -0,0 +1,591 @@ +from odoo.exceptions import UserError + +from .common import TestPms + + +class TestPmsAutomatedMails(TestPms): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.template = cls.env["mail.template"].search( + [("name", "=", "Confirmed Reservation")] + ) + + def test_create_automated_action(self): + """ + Checks that an automated_action is created correctly when an + automated_mail is created. + --------------------- + An automated_mail is created and then it is verified that + the automated_action was created. + """ + # ARRANGE + automated_mail_vals = { + "name": "Auto Mail 1", + "template_id": self.template.id, + "action": "creation", + "moment": "in_act", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + + # ACT + auto_mail = self.env["pms.automated.mails"].create(automated_mail_vals) + + # ASSERT + self.assertTrue( + auto_mail.automated_actions_id, "Automated action should be created " + ) + + def test_no_action_creation_before(self): + """ + Check that an automated mail cannot be created with action='creation' + and moment='before'. + ----------------------- + An automated mail is created with action = 'creation' and moment = 'before'. + Then it is verified that a UserError was thrown because an automated_mail with + these parameters cannot be created. + """ + # ARRANGE + automated_mail_vals = { + "name": "Auto Mail 1", + "template_id": self.template.id, + "action": "creation", + "moment": "before", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + + # ACT & ASSERT + with self.assertRaises( + UserError, + msg="It should not be allowed to create the automated mail " + "with action 'creation' and moment 'before' values", + ): + self.env["pms.automated.mails"].create(automated_mail_vals) + + def test_trigger_moment_in_act_creation(self): + """ + Check that when creating an automated mail with parameters + action = 'creation' and moment = 'in_act' the trigger of the + automated_action created is 'on_create'. + """ + # ARRANGE + automated_mail_vals = { + "name": "Auto Mail 1", + "template_id": self.template.id, + "action": "creation", + "moment": "in_act", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + + # ACT + auto_mail = self.env["pms.automated.mails"].create(automated_mail_vals) + + # ASSERT + self.assertEqual( + auto_mail.automated_actions_id.trigger, + "on_create", + "The trigger of the automated action must be 'on_create'", + ) + + def test_trigger_moment_after_in_creation_action(self): + """ + Check that when creating an automated mail with parameters + action = 'creation' and moment = 'after' the trigger of the + automated_action created is 'on_time'. + """ + # ARRANGE + automated_mail_vals = { + "name": "Auto Mail 1", + "template_id": self.template.id, + "action": "creation", + "moment": "after", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + "time": 1, + "time_type": "hour", + } + + # ACT + auto_mail = self.env["pms.automated.mails"].create(automated_mail_vals) + + # ASSERT + self.assertEqual( + auto_mail.automated_actions_id.trigger, + "on_time", + "The trigger of the automated action must be 'on_time'", + ) + + def test_trigger_moment_in_act_in_write_action(self): + """ + Check that when creating an automated mail with parameters + action = 'write' and moment = 'in_act' the trigger of the + automated_action created is 'on_write'. + """ + # ARRANGE + automated_mail_vals = { + "name": "Auto Mail 1", + "template_id": self.template.id, + "action": "write", + "moment": "in_act", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + + # ACT + auto_mail = self.env["pms.automated.mails"].create(automated_mail_vals) + + # ASSERT + self.assertEqual( + auto_mail.automated_actions_id.trigger, + "on_write", + "The trigger of the automated action must be 'on_write'", + ) + + def test_trigger_moment_after_in_write_action(self): + """ + Check that when creating an automated mail with parameters + action = 'write' and moment = 'after' the trigger of the + automated_action created is 'on_time'. + """ + # ARRANGE + automated_mail_vals = { + "name": "Auto Mail 1", + "template_id": self.template.id, + "action": "write", + "moment": "after", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + "time": 1, + "time_type": "hour", + } + + # ACT + auto_mail = self.env["pms.automated.mails"].create(automated_mail_vals) + + # ASSERT + self.assertEqual( + auto_mail.automated_actions_id.trigger, + "on_time", + "The trigger of the automated action must be 'on_time'", + ) + + def test_time_moment_before_in_checkin(self): + """ + Check that when creating an automated mail with parameters + action = 'checkin' and moment = 'before' the trg_date_range + of the automated_action created is equal to + (automated_mail.time * -1)'. + """ + # ARRANGE + automated_mail_vals = { + "name": "Auto Mail 1", + "template_id": self.template.id, + "action": "checkin", + "moment": "before", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + "time": 60, + "time_type": "minutes", + } + + # ACT + auto_mail = self.env["pms.automated.mails"].create(automated_mail_vals) + + # ASSERT + self.assertEqual( + auto_mail.automated_actions_id.trg_date_range, + -60, + "The trg_date_range of the automated action must be '-60'", + ) + + def test_time_moment_in_act_in_checkin(self): + """ + Check that when creating an automated mail with parameters + action = 'checkin' and moment = 'in_act' the trg_date_range + of the automated_action created is equal to 0 + """ + # ARRANGE + automated_mail_vals = { + "name": "Auto Mail 1", + "template_id": self.template.id, + "action": "checkin", + "moment": "in_act", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + + # ACT + auto_mail = self.env["pms.automated.mails"].create(automated_mail_vals) + + # ASSERT + self.assertEqual( + auto_mail.automated_actions_id.trg_date_range, + 0, + "The trg_date_range of the automated action must be '0'", + ) + + def test_trigger_moment_before_in_checkin(self): + """ + Check that when creating an automated mail with parameters + action = 'checkin' and moment = 'before' the trigger of the + automated_action created is 'on_time'. + """ + # ARRANGE + automated_mail_vals = { + "name": "Auto Mail 1", + "template_id": self.template.id, + "action": "checkin", + "moment": "before", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + "time": 24, + "time_type": "hour", + } + + # ACT + auto_mail = self.env["pms.automated.mails"].create(automated_mail_vals) + + # ASSERT + self.assertEqual( + auto_mail.automated_actions_id.trigger, + "on_time", + "The trigger of the automated action must be 'on_time'", + ) + + def test_trigger_moment_in_act_in_checkin(self): + """ + Check that when creating an automated mail with parameters + action = 'checkin' and moment = 'in_act' the trigger of the + automated_action created is 'on_write'. + """ + # ARRANGE + automated_mail_vals = { + "name": "Auto Mail 1", + "template_id": self.template.id, + "action": "checkin", + "moment": "in_act", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + + # ACT + auto_mail = self.env["pms.automated.mails"].create(automated_mail_vals) + + # ASSERT + self.assertEqual( + auto_mail.automated_actions_id.trigger, + "on_write", + "The trigger of the automated action must be 'on_write'", + ) + + def test_time_moment_in_act_in_checkout(self): + """ + Check that when creating an automated mail with parameters + action = 'checkout' and moment = 'in_act' the trg_date_range + of the automated_action created is equal to 0. + """ + # ARRANGE + automated_mail_vals = { + "name": "Auto Mail 1", + "template_id": self.template.id, + "action": "checkout", + "moment": "in_act", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + + # ACT + auto_mail = self.env["pms.automated.mails"].create(automated_mail_vals) + + # ASSERT + self.assertEqual( + auto_mail.automated_actions_id.trg_date_range, + 0, + "The trg_date_range of the automated action must be '0'", + ) + + def test_trigger_moment_before_in_checkout(self): + """ + Check that when creating an automated mail with parameters + action = 'checkout' and moment = 'before' the trigger of the + automated_action created is 'on_time'. + """ + # ARRANGE + automated_mail_vals = { + "name": "Auto Mail 1", + "template_id": self.template.id, + "action": "checkout", + "moment": "before", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + "time": 24, + "time_type": "hour", + } + + # ACT + auto_mail = self.env["pms.automated.mails"].create(automated_mail_vals) + + # ASSERT + self.assertEqual( + auto_mail.automated_actions_id.trigger, + "on_time", + "The trigger of the automated action must be 'on_time'", + ) + + def test_trigger_moment_in_act_in_checkout(self): + """ + Check that when creating an automated mail with parameters + action = 'checkout' and moment = 'in_act' the trigger of the + automated_action created is 'on_write'. + """ + # ARRANGE + automated_mail_vals = { + "name": "Auto Mail 1", + "template_id": self.template.id, + "action": "checkout", + "moment": "in_act", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + + # ACT + auto_mail = self.env["pms.automated.mails"].create(automated_mail_vals) + + # ASSERT + self.assertEqual( + auto_mail.automated_actions_id.trigger, + "on_write", + "The trigger of the automated action must be 'on_write'", + ) + + def test_trigger_moment_in_act_in_payment_action(self): + """ + Check that when creating an automated mail with parameters + action = 'payment' and moment = 'in_act' the trigger of the + automated_action created is 'on_create'. + """ + # ARRANGE + automated_mail_vals = { + "name": "Auto Mail 1", + "template_id": self.template.id, + "action": "payment", + "moment": "in_act", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + + # ACT + auto_mail = self.env["pms.automated.mails"].create(automated_mail_vals) + + # ASSERT + self.assertEqual( + auto_mail.automated_actions_id.trigger, + "on_create", + "The trigger of the automated action must be 'on_create'", + ) + + def test_trigger_moment_before_in_payment_action(self): + """ + Check that when creating an automated mail with parameters + action = 'payment' and moment = 'before' the trigger of the + automated_action created is 'on_time'. + """ + # ARRANGE + automated_mail_vals = { + "name": "Auto Mail 1", + "template_id": self.template.id, + "action": "payment", + "moment": "before", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + "time": 24, + "time_type": "hour", + } + + # ACT + auto_mail = self.env["pms.automated.mails"].create(automated_mail_vals) + + # ASSERT + self.assertEqual( + auto_mail.automated_actions_id.trigger, + "on_time", + "The trigger of the automated action must be 'on_time'", + ) + + def test_time_moment_before_in_payment_action(self): + """ + Check that when creating an automated mail with parameters + action = 'payment' and moment = 'before' the trg_date_range + field of the automated_action is (automated_mail.time * -1). + """ + # ARRANGE + automated_mail_vals = { + "name": "Auto Mail 1", + "template_id": self.template.id, + "action": "payment", + "moment": "before", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + "time": 24, + "time_type": "hour", + } + + # ACT + auto_mail = self.env["pms.automated.mails"].create(automated_mail_vals) + + # ASSERT + self.assertEqual( + auto_mail.automated_actions_id.trg_date_range, + -24, + "The trg_date_range of the automated action must be '-24'", + ) + + def test_trigger_moment_in_act_in_invoice_action(self): + """ + Check that when creating an automated mail with parameters + action = 'invoice' and moment = 'in_act' the trigger field + of the automated_action created is 'on_create'. + """ + # ARRANGE + automated_mail_vals = { + "name": "Auto Mail 1", + "template_id": self.template.id, + "action": "invoice", + "moment": "in_act", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + + # ACT + auto_mail = self.env["pms.automated.mails"].create(automated_mail_vals) + + # ASSERT + self.assertEqual( + auto_mail.automated_actions_id.trigger, + "on_create", + "The trigger of the automated action must be 'on_create'", + ) + + def test_time_moment_in_act_in_invoice_action(self): + """ + Check that when creating an automated mail with parameters + action = 'invoice' and moment = 'in_act' the trg_date_range + field of the automated_action is 0. + """ + # ARRANGE + automated_mail_vals = { + "name": "Auto Mail 1", + "template_id": self.template.id, + "action": "invoice", + "moment": "in_act", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + + # ACT + auto_mail = self.env["pms.automated.mails"].create(automated_mail_vals) + + # ASSERT + self.assertEqual( + auto_mail.automated_actions_id.trg_date_range, + 0, + "The trg_date_range of the automated action must be '0'", + ) + + def test_filter_pre_domain_moment_in_act_in_checkin(self): + """ + Check that when creating an automated mail with parameters + action = 'checkin' and moment = 'in_act' the filter_pre_domain + field of the automated_action is [('state', '=', 'confirm')]. + """ + # ARRANGE + automated_mail_vals = { + "name": "Auto Mail 1", + "template_id": self.template.id, + "action": "checkin", + "moment": "in_act", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + + # ACT + auto_mail = self.env["pms.automated.mails"].create(automated_mail_vals) + + # ASSERT + self.assertEqual( + auto_mail.automated_actions_id.filter_pre_domain, + "[('state', '=', 'confirm')]", + "The filter_pre_domain of the automated action " + "must be '[('state', '=', 'confirm')]'", + ) + + def test_filter_domain_moment_in_act_in_checkin(self): + """ + Check that when creating an automated mail with parameters + action = 'checkin' and moment = 'in_act' the filter_domain + field of the automated_action is + [('state', '=', 'onboard'), ('pms_property_id', '=', [value of property_id.id])]]. + """ + # ARRANGE + automated_mail_vals = { + "name": "Auto Mail 1", + "template_id": self.template.id, + "action": "checkin", + "moment": "in_act", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + + # ACT + auto_mail = self.env["pms.automated.mails"].create(automated_mail_vals) + pms_property_id_str = str(auto_mail.pms_property_ids.ids) + + # ASSERT + self.assertEqual( + auto_mail.automated_actions_id.filter_domain, + "[('state', '=', 'onboard'), ('pms_property_id', 'in', " + + pms_property_id_str + + ")]", + "The filter_domain of the automated action must be " + "'[('state', '=', 'onboard'), " + "('pms_property_id', '=', [value of property_id.id])]'", + ) + + def test_filter_pre_domain_moment_in_act_in_checkout(self): + """ + Check that when creating an automated mail with parameters + action = 'checkout' and moment = 'in_act' the filter_pre_domain + field of the automated_action is [('state', '=', 'onboard')]. + """ + # ARRANGE + automated_mail_vals = { + "name": "Auto Mail 1", + "template_id": self.template.id, + "action": "checkout", + "moment": "in_act", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + + # ACT + auto_mail = self.env["pms.automated.mails"].create(automated_mail_vals) + + # ASSERT + self.assertEqual( + auto_mail.automated_actions_id.filter_pre_domain, + "[('state', '=', 'onboard')]", + "The filter_pre_domain of the automated action must " + "be '[('state', '=', 'onboard')]'", + ) + + def test_filter_domain_moment_in_act_in_checkout(self): + """ + Check that when creating an automated mail with parameters + action = 'checkout' and moment = 'in_act' the filter_domain + field of the automated_action is + [('state', '=', 'out'), ('pms_property_id', '=', [value of property_id.id])]]. + """ + # ARRANGE + automated_mail_vals = { + "name": "Auto Mail 1", + "template_id": self.template.id, + "action": "checkout", + "moment": "in_act", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + + # ACT + auto_mail = self.env["pms.automated.mails"].create(automated_mail_vals) + pms_property_id_str = str(auto_mail.pms_property_ids.ids) + + # ASSERT + self.assertEqual( + auto_mail.automated_actions_id.filter_domain, + "[('state', '=', 'done'), ('pms_property_id', 'in', " + + pms_property_id_str + + ")]", + "The filter_pre_domain of the automated action must " + "be '[('state', '=', 'out'), ('pms_property_id', '=', [value of property_id.id])]", + ) diff --git a/pms/tests/test_pms_availability_plan_rules.py b/pms/tests/test_pms_availability_plan_rules.py new file mode 100644 index 0000000000..528ad9c364 --- /dev/null +++ b/pms/tests/test_pms_availability_plan_rules.py @@ -0,0 +1,629 @@ +import datetime + +from odoo import fields +from odoo.exceptions import ValidationError + +from .common import TestPms + + +class TestPmsRoomTypeAvailabilityRules(TestPms): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.pms_property2 = cls.env["pms.property"].create( + { + "name": "Property 2", + "company_id": cls.company1.id, + "default_pricelist_id": cls.pricelist1.id, + } + ) + cls.pricelist2 = cls.env["product.pricelist"].create( + { + "name": "test pricelist 1", + "pms_property_ids": [ + (4, cls.pms_property1.id), + (4, cls.pms_property2.id), + ], + "availability_plan_id": cls.availability_plan1.id, + "is_pms_available": True, + } + ) + # pms.sale.channel + cls.sale_channel_direct1 = cls.env["pms.sale.channel"].create( + { + "name": "Door", + "channel_type": "direct", + } + ) + # pms.availability.plan + cls.test_room_type_availability1 = cls.env["pms.availability.plan"].create( + { + "name": "Availability plan for TEST", + "pms_pricelist_ids": [(6, 0, [cls.pricelist2.id])], + } + ) + # pms.property + cls.pms_property3 = cls.env["pms.property"].create( + { + "name": "MY PMS TEST", + "company_id": cls.company1.id, + "default_pricelist_id": cls.pricelist2.id, + } + ) + cls.pricelist2.write( + { + "pms_property_ids": [ + (4, cls.pms_property3.id), + ], + } + ) + + # pms.room.type + cls.test_room_type_single = cls.env["pms.room.type"].create( + { + "pms_property_ids": [cls.pms_property3.id], + "name": "Single Test", + "default_code": "SNG_Test", + "class_id": cls.room_type_class1.id, + } + ) + # pms.room.type + cls.test_room_type_double = cls.env["pms.room.type"].create( + { + "pms_property_ids": [ + (4, cls.pms_property3.id), + ], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": cls.room_type_class1.id, + } + ) + # pms.room + cls.test_room1_double = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property3.id, + "name": "Double 201 test", + "room_type_id": cls.test_room_type_double.id, + "capacity": 2, + } + ) + # pms.room + cls.test_room2_double = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property3.id, + "name": "Double 202 test", + "room_type_id": cls.test_room_type_double.id, + "capacity": 2, + } + ) + cls.test_room1_single = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property3.id, + "name": "Single 101 test", + "room_type_id": cls.test_room_type_single.id, + "capacity": 1, + } + ) + # pms.room + cls.test_room2_single = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property3.id, + "name": "Single 102 test", + "room_type_id": cls.test_room_type_single.id, + "capacity": 1, + } + ) + # partner + cls.partner1 = cls.env["res.partner"].create( + {"name": "Charles", "property_product_pricelist": cls.pricelist1} + ) + + def test_availability_rooms_all(self): + """ + Check the availability of rooms in a property with an availability plan without + availability rules. + --------------------- + The checkin and checkout dates on which the availability will be checked are saved + in a variable and in another all the rooms of the property are also saved. Then the + free_room_ids compute field is called which should return the number of available rooms + of the property and they are saved in another variable with which it is verified that + all the rooms have been returned because there are no availability rules for that plan. + """ + + # ARRANGE + checkin = fields.date.today() + checkout = (fields.datetime.today() + datetime.timedelta(days=4)).date() + test_rooms_double_rooms = self.env["pms.room"].search( + [("pms_property_id", "=", self.pms_property3.id)] + ) + # ACT + pms_property = self.pms_property3.with_context( + checkin=checkin, + checkout=checkout, + ) + result = pms_property.free_room_ids + + # ASSERT + obtained = all(elem.id in result.ids for elem in test_rooms_double_rooms) + self.assertTrue( + obtained, + "Availability should contain the test rooms" + "because there's no availability rules for them.", + ) + + def test_plan_avail_update_to_one(self): + """ + Check that the plan avail on a room is updated when the real avail is changed + -------------------------------------------------------------- + Room type with 2 rooms, new reservation with this room type, the plan avail + must to be updated to 1. You must know that the pricelist2 is linked + with the plan test_room_type_availability1 + """ + # ARRANGE + checkin = fields.date.today() + checkout = (fields.datetime.today() + datetime.timedelta(days=4)).date() + room_type = self.test_room_type_double + + self.test_room_type_availability_rule1 = self.env[ + "pms.availability.plan.rule" + ].create( + { + "availability_plan_id": self.test_room_type_availability1.id, + "room_type_id": self.test_room_type_double.id, + "date": fields.datetime.today(), + "pms_property_id": self.pms_property3.id, + } + ) + # ACT + self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property3.id, + "checkin": checkin, + "checkout": checkout, + "partner_id": self.partner1.id, + "room_type_id": room_type.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + result = self.test_room_type_availability_rule1.plan_avail + + # ASSERT + self.assertEqual( + result, + 1, + "There should be only one room in the result of the availability plan" + "because the real avail is 1 and the availability plan" + "is updated.", + ) + + def test_plan_avail_update_to_zero(self): + """ + Check that the plan avail on a room is updated when the real avail is changed + with real avail 0 + -------------------------------------------------------------- + Room type with 2 rooms, two new reservations with this room type, the plan avail + must to be updated to 0. You must know that the pricelist2 is linked + with the plan test_room_type_availability1 + """ + # ARRANGE + checkin = fields.date.today() + checkout = (fields.datetime.today() + datetime.timedelta(days=4)).date() + room_type = self.test_room_type_double + + self.test_room_type_availability_rule1 = self.env[ + "pms.availability.plan.rule" + ].create( + { + "availability_plan_id": self.test_room_type_availability1.id, + "room_type_id": self.test_room_type_double.id, + "date": fields.datetime.today(), + "pms_property_id": self.pms_property3.id, + } + ) + # ACT + self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property3.id, + "checkin": checkin, + "checkout": checkout, + "partner_id": self.partner1.id, + "room_type_id": room_type.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property3.id, + "checkin": checkin, + "checkout": checkout, + "partner_id": self.partner1.id, + "room_type_id": room_type.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + result = self.test_room_type_availability_rule1.plan_avail + + # ASSERT + self.assertEqual( + result, + 0, + "There should be zero in the result of the availability plan" + "because the real avail is 0 and the availability plan" + "is updated.", + ) + + def test_availability_rooms_all_lines(self): + """ + Check the availability of rooms in a property with an availability plan without + availability rules and passing it the reservation lines of a reservation for that + property. + ----------------- + The checkin and checkout dates on which the availability will be checked are saved + in a variable and in another all the rooms of the property are also saved. Then create + a reservation for this property and the free_room_ids compute field is called with the + parameters checkin, checkout and the reservation lines of the reservation as a curent + lines, this method should return the number of available rooms of the property. Then the + result is saved in another variable with which it is verified that all the rooms have + been returned because there are no availability rules for that plan. + """ + + # ARRANGE + checkin = fields.date.today() + checkout = (fields.datetime.today() + datetime.timedelta(days=4)).date() + test_rooms_double_rooms = self.env["pms.room"].search( + [("pms_property_id", "=", self.pms_property3.id)] + ) + test_reservation = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property3.id, + "checkin": checkin, + "checkout": checkout, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + # ACT + # REVIEW: reservation without room and wihout room type? + pms_property = self.pms_property3.with_context( + checkin=checkin, + checkout=checkout, + current_lines=test_reservation.reservation_line_ids.ids, + ) + result = pms_property.free_room_ids + + # ASSERT + obtained = all(elem.id in result.ids for elem in test_rooms_double_rooms) + self.assertTrue( + obtained, + "Availability should contain the test rooms" + "because there's no availability rules for them.", + ) + + def test_availability_rooms_room_type(self): + """ + Check the availability of a room type for a property. + ---------------- + Double rooms of a property are saved in a variable. The free_room_ids compute field + is called giving as parameters checkin, checkout and the type of room (in this + case double). Then with the all () function we check that all rooms of this type + were returned. + """ + + # ARRANGE + test_rooms_double_rooms = self.env["pms.room"].search( + [ + ("pms_property_id", "=", self.pms_property3.id), + ("room_type_id", "=", self.test_room_type_double.id), + ] + ) + # ACT + pms_property = self.pms_property3.with_context( + checkin=fields.date.today(), + checkout=(fields.datetime.today() + datetime.timedelta(days=4)).date(), + room_type_id=self.test_room_type_double.id, + ) + result = pms_property.free_room_ids + + # ASSERT + obtained = all(elem.id in result.ids for elem in test_rooms_double_rooms) + self.assertTrue( + obtained, + "Availability should contain the test rooms" + "because there's no availability rules for them.", + ) + + def test_availability_closed_no_room_type(self): + """ + Check that rooms of a type with an availability rule with closed = True are + not available on the dates marked in the date field of the availability rule. + -------------------- + Create an availability rule for double rooms with the field closed = true + and the date from today until tomorrow. Then the availability is saved in a + variable through the free_room_ids computed field, passing it the pricelist that + it contains the availability plan where the rule is included, and the checkin + and checkout dates are between the date of the rule. Then it is verified that + the double rooms are not available. + """ + # ARRANGE + self.test_room_type_availability_rule1 = self.env[ + "pms.availability.plan.rule" + ].create( + { + "availability_plan_id": self.test_room_type_availability1.id, + "room_type_id": self.test_room_type_double.id, + "date": (fields.datetime.today() + datetime.timedelta(days=2)).date(), + "closed": True, # <- (1/2) + "pms_property_id": self.pms_property3.id, + } + ) + # ACT + pms_property = self.pms_property3.with_context( + checkin=fields.date.today(), + checkout=(fields.datetime.today() + datetime.timedelta(days=4)).date(), + # room_type_id=False, # <- (2/2) + pricelist_id=self.pricelist2.id, + ) + result = pms_property.free_room_ids + + # ASSERT + self.assertNotIn( + self.test_room_type_double, + result.mapped("room_type_id"), + "Availability should not contain rooms of a type " + "which its availability rules applies", + ) + + def test_availability_rules(self): + """ + Check through subtests that the availability rules are applied + for a specific room type. + ---------------- + Test cases: + 1. closed_arrival = True + 2. closed_departure = True + 3. min_stay = 5 + 4. max_stay = 2 + 5. min_stay_arrival = 5 + 6. max_stay_arrival = 3 + 7. quota = 0 + 8. max_avail = 0 + For each test case, it is verified through the free_room_ids compute field, + that double rooms are not available since the rules are applied to this + room type. + """ + + # ARRANGE + + self.test_room_type_availability_rule1 = self.env[ + "pms.availability.plan.rule" + ].create( + { + "availability_plan_id": self.test_room_type_availability1.id, + "room_type_id": self.test_room_type_double.id, + "date": fields.date.today(), + "pms_property_id": self.pms_property3.id, + } + ) + + checkin = fields.date.today() + checkout = (fields.datetime.today() + datetime.timedelta(days=4)).date() + + test_cases = [ + { + "closed": False, + "closed_arrival": True, + "closed_departure": False, + "min_stay": 0, + "max_stay": 0, + "min_stay_arrival": 0, + "max_stay_arrival": 0, + "quota": -1, + "max_avail": -1, + "date": checkin, + }, + { + "closed": False, + "closed_arrival": False, + "closed_departure": True, + "min_stay": 0, + "max_stay": 0, + "min_stay_arrival": 0, + "max_stay_arrival": 0, + "quota": -1, + "max_avail": -1, + "date": checkout, + }, + { + "closed": False, + "closed_arrival": False, + "closed_departure": False, + "min_stay": 5, + "max_stay": 0, + "min_stay_arrival": 0, + "max_stay_arrival": 0, + "quota": -1, + "max_avail": -1, + "date": checkin, + }, + { + "closed": False, + "closed_arrival": False, + "closed_departure": False, + "min_stay": 0, + "max_stay": 2, + "min_stay_arrival": 0, + "max_stay_arrival": 0, + "quota": -1, + "max_avail": -1, + "date": checkin, + }, + { + "closed": False, + "closed_arrival": False, + "closed_departure": False, + "min_stay": 0, + "max_stay": 0, + "min_stay_arrival": 5, + "max_stay_arrival": 0, + "quota": -1, + "max_avail": -1, + "date": checkin, + }, + { + "closed": False, + "closed_arrival": False, + "closed_departure": False, + "min_stay": 0, + "max_stay": 0, + "min_stay_arrival": 0, + "max_stay_arrival": 3, + "quota": -1, + "max_avail": -1, + "date": checkin, + }, + { + "closed": False, + "closed_arrival": False, + "closed_departure": False, + "min_stay": 0, + "max_stay": 0, + "min_stay_arrival": 0, + "max_stay_arrival": 0, + "quota": 0, + "max_avail": -1, + "date": checkin, + }, + { + "closed": False, + "closed_arrival": False, + "closed_departure": False, + "min_stay": 0, + "max_stay": 0, + "min_stay_arrival": 0, + "max_stay_arrival": 0, + "quota": -1, + "max_avail": 0, + "date": checkin, + }, + ] + + for test_case in test_cases: + with self.subTest(k=test_case): + + # ACT + self.test_room_type_availability_rule1.write(test_case) + + pms_property = self.pms_property3.with_context( + checkin=checkin, + checkout=checkout, + room_type_id=self.test_room_type_double.id, + pricelist_id=self.pricelist2.id, + ) + result = pms_property.free_room_ids + + # ASSERT + self.assertNotIn( + self.test_room_type_double, + result.mapped("room_type_id"), + "Availability should not contain rooms of a type " + "which its availability rules applies", + ) + + def test_rule_on_create_reservation(self): + """ + Check that a reservation is not created when an availability rule prevents it . + ------------------- + Create an availability rule for double rooms with the + field closed = True and the date from today until tomorrow. Then try to create + a reservation for that type of room with a checkin date today and a checkout + date within 4 days. This should throw a ValidationError since the rule does + not allow creating reservations for those dates. + """ + + # ARRANGE + self.test_room_type_availability_rule1 = self.env[ + "pms.availability.plan.rule" + ].create( + { + "availability_plan_id": self.test_room_type_availability1.id, + "room_type_id": self.test_room_type_double.id, + "date": (fields.datetime.today() + datetime.timedelta(days=2)).date(), + "closed": True, + "pms_property_id": self.pms_property3.id, + } + ) + checkin = datetime.datetime.now() + checkout = datetime.datetime.now() + datetime.timedelta(days=4) + + # ACT & ASSERT + with self.assertRaises( + ValidationError, + msg="Availability rules should be applied that would" + " prevent the creation of the reservation.", + ): + self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property3.id, + "checkin": checkin, + "checkout": checkout, + "adults": 2, + "room_type_id": self.test_room_type_double.id, + "pricelist_id": self.pricelist2.id, + "partner_id": self.partner1.id, + } + ) + + def test_rule_update_quota_on_create_reservation(self): + """ + Check that the availability rule with quota = 1 for a room + type does not allow you to create more reservations than 1 + for that room type. + """ + + # ARRANGE + + self.test_room_type_availability_rule1 = self.env[ + "pms.availability.plan.rule" + ].create( + { + "availability_plan_id": self.test_room_type_availability1.id, + "room_type_id": self.test_room_type_double.id, + "date": datetime.date.today(), + "quota": 1, + "pms_property_id": self.pms_property3.id, + } + ) + self.pricelist2.pms_property_ids = [ + (4, self.pms_property1.id), + (4, self.pms_property2.id), + (4, self.pms_property3.id), + ] + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property3.id, + "checkin": datetime.date.today(), + "checkout": datetime.date.today() + datetime.timedelta(days=1), + "adults": 2, + "room_type_id": self.test_room_type_double.id, + "pricelist_id": self.pricelist2.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r1.flush() + with self.assertRaises( + ValidationError, + msg="The quota shouldnt be enough to create a new reservation", + ): + self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property3.id, + "checkin": datetime.date.today(), + "checkout": datetime.date.today() + datetime.timedelta(days=1), + "adults": 2, + "room_type_id": self.test_room_type_double.id, + "pricelist_id": self.pricelist2.id, + "partner_id": self.partner1.id, + } + ) diff --git a/pms/tests/test_pms_board_service.py b/pms/tests/test_pms_board_service.py new file mode 100644 index 0000000000..43cf24839c --- /dev/null +++ b/pms/tests/test_pms_board_service.py @@ -0,0 +1,411 @@ +# Copyright 2021 Eric Antones +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.exceptions import ValidationError + +from .common import TestPms + + +class TestBoardService(TestPms): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.company2 = cls.env["res.company"].create( + { + "name": "Company 2", + } + ) + cls.pms_property3 = cls.env["pms.property"].create( + { + "name": "Property 3", + "company_id": cls.company2.id, + "default_pricelist_id": cls.pricelist1.id, + } + ) + + def test_create_bs_one_company_inconsistent_code(self): + """ + Creation of board service with the same code as an existing one + belonging to the same property should fail. + + PRE: - board service bs1 exists + - board_service1 has code c1 + - board_service1 has pms_property1 + - pms_property1 has company company1 + ACT: - create a new board_service2 + - board_service2 has code c1 + - board_service2 has pms_property1 + - pms_property1 has company company1 + POST: - Integrity error: the room type already exists + - board_service2 not created + """ + # ARRANGE + # board_service1 + self.env["pms.board.service"].create( + { + "name": "Board service bs1", + "default_code": "c1", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + ) + # ACT & ASSERT + with self.assertRaises( + ValidationError, msg="The board service has been created and it shouldn't" + ): + # board_service2 + self.env["pms.board.service"].create( + { + "name": "Board service bs2", + "default_code": "c1", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + ) + + def test_create_bs_several_companies_inconsistent_code(self): + """ + Creation of board service with properties and one of its + properties has the same code on its board services should fail. + + PRE: - board service bs1 exists + - board_service1 has code c1 + - board_service1 has property pms_property1 + - pms_property1 has company company1 + ACT: - create a new board_service2 + - board_service2 has code c1 + - board_service2 has property pms_property1, pms_property2, + pms_property3 + - pms_property1, pms_property2 has company company1 + - pms_property3 has company company2 + POST: - Integrity error: the board service already exists + - board_service2 not created + """ + # ARRANGE + self.pms_property2 = self.env["pms.property"].create( + { + "name": "Property 2", + "company_id": self.company1.id, + "default_pricelist_id": self.pricelist1.id, + } + ) + # board_service1 + self.env["pms.board.service"].create( + { + "name": "Board service 1", + "default_code": "c1", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + ) + # ACT & ASSERT + with self.assertRaises( + ValidationError, msg="The board service has been created and it shouldn't" + ): + # board_service2 + self.env["pms.board.service"].create( + { + "name": "Board service bs2", + "default_code": "c1", + "pms_property_ids": [ + ( + 6, + 0, + [ + self.pms_property1.id, + self.pms_property2.id, + self.pms_property3.id, + ], + ) + ], + } + ) + + def test_search_bs_code_same_company_several_properties(self): + """ + Checks the search for a board service by code when the board service + belongs to properties of the same company + + PRE: - board service bs1 exists + - board_service1 has code c1 + - board_service1 has 2 properties pms_property1 and pms_property2 + - pms_property_1 and pms_property2 have the same company company1 + ACT: - search board service with code c1 and pms_property1 + - pms_property1 has company company1 + POST: - only board_service1 board service found + """ + # ARRANGE + self.pms_property2 = self.env["pms.property"].create( + { + "name": "Property 2", + "company_id": self.company1.id, + "default_pricelist_id": self.pricelist1.id, + } + ) + board_service1 = self.env["pms.board.service"].create( + { + "name": "Board service 1", + "default_code": "c1", + "pms_property_ids": [ + (6, 0, [self.pms_property1.id, self.pms_property2.id]) + ], + } + ) + # ACT + board_services = self.env["pms.board.service"].get_unique_by_property_code( + self.pms_property1.id, "c1" + ) + # ASSERT + self.assertEqual( + board_services.id, + board_service1.id, + "Expected board service not found", + ) + + def test_search_bs_code_several_companies_several_properties_not_found(self): + """ + Checks the search for a board service by code when the board service + belongs to properties with different companies + + PRE: - board service bs1 exists + - board_service1 has code c1 + - board_service1 has 2 properties pms_property1 and pms_property3 + - pms_property1 and pms_property3 have different companies + - pms_property1 have company company1 and pms_property3 have company2 + ACT: - search board service with code c1 and property pms_property1 + - pms_property1 has company company1 + POST: - only board_service1 room type found + """ + # ARRANGE + bs1 = self.env["pms.board.service"].create( + { + "name": "Board service 1", + "default_code": "c1", + "pms_property_ids": [ + (6, 0, [self.pms_property1.id, self.pms_property3.id]) + ], + } + ) + # ACT + board_services = self.env["pms.board.service"].get_unique_by_property_code( + self.pms_property1.id, "c1" + ) + # ASSERT + self.assertEqual(board_services.id, bs1.id, "Expected board service not found") + + def test_search_bs_code_no_result(self): + """ + Search for a specific board service code and its property. + The board service exists but not in the property given. + + PRE: - board_service1 exists + - board_service1 has code c1 + - board_service1 with 2 properties pms_property1 and pms_property2 + - pms_property1 and pms_property2 have same company company1 + ACT: - search board service with code c1 and property pms_property3 + - pms_property3 have company company2 + POST: - no room type found + """ + # ARRANGE + self.pms_property2 = self.env["pms.property"].create( + { + "name": "Property 2", + "company_id": self.company1.id, + "default_pricelist_id": self.pricelist1.id, + } + ) + # board_service1 + self.env["pms.board.service"].create( + { + "name": "Board service 1", + "default_code": "c1", + "pms_property_ids": [ + (6, 0, [self.pms_property1.id, self.pms_property2.id]) + ], + } + ) + # ACT + board_services = self.env["pms.board.service"].get_unique_by_property_code( + self.pms_property3.id, "c1" + ) + # ASSERT + self.assertFalse( + board_services, "Board service found but it should not have found any" + ) + + def test_search_bs_code_present_all_companies_and_properties(self): + """ + Search for a specific board service and its property. + The board service exists without property, then + the search foundS the result. + + PRE: - board_service1 exists + - board_service1 has code c1 + - board_service1 properties are null + ACT: - search board service with code c1 and property pms_property1 + - pms_property1 have company company1 + POST: - only board_service1 board service found + """ + # ARRANGE + board_service1 = self.env["pms.board.service"].create( + { + "name": "Board service 1", + "default_code": "c1", + "pms_property_ids": False, + } + ) + # ACT + board_services = self.env["pms.board.service"].get_unique_by_property_code( + self.pms_property1.id, "c1" + ) + # ASSERT + self.assertEqual( + board_services.id, + board_service1.id, + "Expected board service not found", + ) + + def test_search_bs_code_several_companies_several_properties(self): + """ + Search for a specific board service and its property. + There is one board service without properties and + another one with the same code that belongs to 2 properties + (from different companies) + The search founds only the board service that match the + property given. + + PRE: - board_service1 exists + - board_service1 has code c1 + - board_service1 has 2 properties pms_property1 and pms_property3 + - pms_property1 and pms_property2 have the same company company1 + - board service board_service2 exists + - board_service2 has code c1 + - board_service2 has no properties + ACT: - search board service with code c1 and property pms_property1 + - pms_property1 have company company1 + POST: - only board_service1 board service found + """ + # ARRANGE + board_service1 = self.env["pms.board.service"].create( + { + "name": "Board service 1", + "default_code": "c1", + "pms_property_ids": [ + (6, 0, [self.pms_property1.id, self.pms_property3.id]) + ], + } + ) + # board_service2 + self.env["pms.board.service"].create( + { + "name": "Board service bs2", + "default_code": "c1", + "pms_property_ids": False, + } + ) + # ACT + board_services = self.env["pms.board.service"].get_unique_by_property_code( + self.pms_property1.id, "c1" + ) + # ASSERT + self.assertEqual( + board_services.id, + board_service1.id, + "Expected board service not found", + ) + + def test_search_bs_code_same_companies_several_properties(self): + """ + Search for a specific board service and its property. + There is one board service without properties and + another one with the same code that belongs to 2 properties + (same company). + The search founds only the board service that match the + property given. + + PRE: - board_service1 exists + - board_service1 has code c1 + - board_service1 has property pms_property1 + - pms_property1 have the company company1 + - board service board_service2 exists + - board_service2 has code c1 + - board_service2 has no properties + ACT: - search board service with code c1 and pms_property2 + - pms_property2 have company company1 + POST: - only board_service2 board service found + """ + # ARRANGE + self.pms_property2 = self.env["pms.property"].create( + { + "name": "Property 2", + "company_id": self.company1.id, + "default_pricelist_id": self.pricelist1.id, + } + ) + self.env["pms.board.service"].create( + { + "name": "Board service 1", + "default_code": "c1", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + ) + board_service2 = self.env["pms.board.service"].create( + { + "name": "Board service bs2", + "default_code": "c1", + "pms_property_ids": False, + } + ) + # ACT + board_services = self.env["pms.board.service"].get_unique_by_property_code( + self.pms_property2.id, "c1" + ) + # ASSERT + self.assertEqual( + board_services.id, + board_service2.id, + "Expected board service not found", + ) + + def test_search_bs_code_no_properties(self): + """ + Search for a specific board service and its property. + There is one board service without properties and + another one with the same code that belongs to one property. + The search founds only the board service that match the + property given that it's not the same as the 2nd one. + + PRE: - board_service1 exists + - board_service1 has code c1 + - board_service1 has property pms_property1 + - pms_property1 have the company company1 + - board service board_service2 exists + - board_service2 has code c1 + - board_service2 has no properties + ACT: - search board service with code c1 and property pms_property3 + - pms_property3 have company company2 + POST: - only board_service2 board service found + """ + # ARRANGE + # board_service1 + self.env["pms.board.service"].create( + { + "name": "Board service bs1", + "default_code": "c1", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + ) + board_service2 = self.env["pms.board.service"].create( + { + "name": "Board service bs2", + "default_code": "c1", + "pms_property_ids": False, + } + ) + # ACT + board_services = self.env["pms.board.service"].get_unique_by_property_code( + self.pms_property3.id, "c1" + ) + # ASSERT + self.assertEqual( + board_services.id, + board_service2.id, + "Expected board service not found", + ) diff --git a/pms/tests/test_pms_booking_engine.py b/pms/tests/test_pms_booking_engine.py new file mode 100644 index 0000000000..2f85382402 --- /dev/null +++ b/pms/tests/test_pms_booking_engine.py @@ -0,0 +1,1010 @@ +import datetime + +from freezegun import freeze_time + +from odoo import fields + +from .common import TestPms + + +class TestPmsBookingEngine(TestPms): + @classmethod + def setUpClass(cls): + super().setUpClass() + # CREATION OF ROOM TYPE (WITH ROOM TYPE CLASS) + cls.test_room_type_double = cls.env["pms.room.type"].create( + { + "pms_property_ids": [cls.pms_property1.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": cls.room_type_class1.id, + "list_price": 40.0, + } + ) + + # pms.room + cls.test_room1_double = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property1.id, + "name": "Double 201 test", + "room_type_id": cls.test_room_type_double.id, + "capacity": 2, + } + ) + + # pms.room + cls.test_room2_double = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property1.id, + "name": "Double 202 test", + "room_type_id": cls.test_room_type_double.id, + "capacity": 2, + } + ) + + # pms.room + cls.test_room3_double = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property1.id, + "name": "Double 203 test", + "room_type_id": cls.test_room_type_double.id, + "capacity": 2, + } + ) + + # pms.room + cls.test_room4_double = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property1.id, + "name": "Double 204 test", + "room_type_id": cls.test_room_type_double.id, + "capacity": 2, + } + ) + + # res.partner + cls.partner_id = cls.env["res.partner"].create( + { + "name": "Miguel", + "mobile": "654667733", + "email": "miguel@example.com", + } + ) + + # pms.sale.channel + cls.sale_channel_direct1 = cls.env["pms.sale.channel"].create( + { + "name": "Door", + "channel_type": "direct", + } + ) + + def test_price_wizard_correct(self): + # TEST CASE + """ + Check by subtests if the total_price field is applied correctly + with and without discount. + ------------ + Create two test cases: one with the discount at 0 and with the + expected total price, which is the difference in days between + checkin and checkout, multiplied by the room price and multiplied + by the number of rooms, and another with the discount at 0.5 and with + total price the same as the first. Then the wizard is created and it + is verified that the wizard's total_price_folio is the same as the + expected price. + """ + + # ARRANGE + + # checkin & checkout + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=1) + days = (checkout - checkin).days + num_double_rooms = 4 + discounts = [ + { + "discount": 0, + "expected_price": days + * self.test_room_type_double.list_price + * num_double_rooms, + }, + { + "discount": 0.5, + "expected_price": ( + days * self.test_room_type_double.list_price * num_double_rooms + ) + * 0.5, + }, + ] + + # create folio wizard with partner id => pricelist & start-end dates + booking_engine = self.env["pms.booking.engine"].create( + { + "start_date": checkin, + "end_date": checkout, + "partner_id": self.partner_id.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "channel_type_id": self.sale_channel_direct1.id, + } + ) + + # force pricelist load + + # availability items belonging to test property + lines_availability_test = self.env["pms.folio.availability.wizard"].search( + [ + ("room_type_id.pms_property_ids", "in", self.pms_property1.id), + ] + ) + + # set value for room type double + value = self.env["pms.num.rooms.selection"].search( + [ + ("room_type_id", "=", self.test_room_type_double.id), + ("value", "=", num_double_rooms), + ] + ) + + lines_availability_test[0].num_rooms_selected = value + for discount in discounts: + with self.subTest(k=discount): + # ACT + booking_engine.discount = discount["discount"] + + # ASSERT + self.assertEqual( + booking_engine.total_price_folio, + discount["expected_price"], + "The total price calculation is wrong", + ) + + def test_price_wizard_correct_pricelist_applied(self): + """ + Check that the total_price field is applied correctly in + the wizard(pricelist applied). + ------------------ + Create a pricelist item for pricelist1 and a wizard is also + created with pricelist1. Then it is verified that the value + of the total price of the wizard corresponds to the value of + the price of the pricelist item. + """ + + # ARRANGE + # checkin & checkout + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=1) + days = (checkout - checkin).days + + # num. rooms of type double to book + num_double_rooms = 4 + + # price for today + price_today = 38.0 + + # expected price + expected_price_total = days * price_today * num_double_rooms + + # set pricelist item for current day + product_tmpl = self.test_room_type_double.product_id.product_tmpl_id + self.env["product.pricelist.item"].create( + { + "pricelist_id": self.pricelist1.id, + "date_start_consumption": checkin, + "date_end_consumption": checkin, + "compute_price": "fixed", + "applied_on": "1_product", + "product_tmpl_id": product_tmpl.id, + "product_id": self.test_room_type_double.product_id.id, + "fixed_price": price_today, + "min_quantity": 0, + "pms_property_ids": product_tmpl.pms_property_ids.ids, + } + ) + + # create folio wizard with partner id => pricelist & start-end dates + booking_engine = self.env["pms.booking.engine"].create( + { + "start_date": checkin, + "end_date": checkout, + "partner_id": self.partner_id.id, + "pricelist_id": self.pricelist1.id, + "pms_property_id": self.pms_property1.id, + "channel_type_id": self.sale_channel_direct1.id, + } + ) + + # availability items belonging to test property + lines_availability_test = self.env["pms.folio.availability.wizard"].search( + [ + ("room_type_id.pms_property_ids", "in", self.pms_property1.id), + ] + ) + + # set value for room type double + value = self.env["pms.num.rooms.selection"].search( + [ + ("room_type_id", "=", self.test_room_type_double.id), + ("value", "=", num_double_rooms), + ] + ) + + # ACT + lines_availability_test[0].num_rooms_selected = value + + # ASSERT + self.assertEqual( + booking_engine.total_price_folio, + expected_price_total, + "The total price calculation is wrong", + ) + + # REVIEW: This test is set to check min qty, but the workflow price, actually, + # always is set to 1 qty and the min_qty cant be applied. + # We could set qty to number of rooms?? + + # def test_price_wizard_correct_pricelist_applied_min_qty_applied(self): + # # TEST CASE + # # Set values for the wizard and the total price is correct + # # (pricelist applied) + + # # ARRANGE + # # common scenario + # self.create_common_scenario() + + # # checkin & checkout + # checkin = fields.date.today() + # checkout = fields.date.today() + datetime.timedelta(days=1) + # days = (checkout - checkin).days + + # # set pricelist item for current day + # product_tmpl_id = self.test_room_type_double.product_id.product_tmpl_id.id + # pricelist_item = self.env["product.pricelist.item"].create( + # { + # "pricelist_id": self.test_pricelist.id, + # "date_start_consumption": checkin, + # "date_end_consumption": checkin, + # "compute_price": "fixed", + # "applied_on": "1_product", + # "product_tmpl_id": product_tmpl_id, + # "fixed_price": 38.0, + # "min_quantity": 4, + # } + # ) + + # # create folio wizard with partner id => pricelist & start-end dates + # booking_engine = self.env["pms.booking.engine"].create( + # { + # "start_date": checkin, + # "end_date": checkout, + # "partner_id": self.partner_id.id, + # "pricelist_id": self.test_pricelist.id, + # } + # ) + + # # availability items belonging to test property + # lines_availability_test = self.env["pms.folio.availability.wizard"].search( + # [ + # ("room_type_id.pms_property_ids", "in", self.test_property.id), + # ] + # ) + + # test_cases = [ + # { + # "num_rooms": 3, + # "expected_price": 3 * self.test_room_type_double.list_price * days, + # }, + # {"num_rooms": 4, "expected_price": 4 * pricelist_item.fixed_price * days}, + # ] + # import wdb; wdb.set_trace() + # for tc in test_cases: + # with self.subTest(k=tc): + # # ARRANGE + # # set value for room type double + # value = self.env["pms.num.rooms.selection"].search( + # [ + # ("room_type_id", "=", self.test_room_type_double.id), + # ("value", "=", tc["num_rooms"]), + # ] + # ) + # # ACT + # lines_availability_test[0].num_rooms_selected = value + + # # ASSERT + # self.assertEqual( + # booking_engine.total_price_folio, + # tc["expected_price"], + # "The total price calculation is wrong", + # ) + + def test_check_create_folio(self): + """ + Test that a folio is created correctly from the booking engine wizard. + ------------ + The wizard is created with a partner_id, a pricelist, and start and end + dates for property1. The availability items are searched for that property + and in the first one a double room is set. The create_folio() method of the + wizard is launched. The folios of the partner_id entered in the wizard are + searched and it is verified that the folio exists. + """ + + # ARRANGE + + # checkin & checkout + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=1) + + # create folio wizard with partner id => pricelist & start-end dates + booking_engine = self.env["pms.booking.engine"].create( + { + "start_date": checkin, + "end_date": checkout, + "partner_id": self.partner_id.id, + "pricelist_id": self.pricelist1.id, + "pms_property_id": self.pms_property1.id, + "channel_type_id": self.sale_channel_direct1.id, + } + ) + + # availability items belonging to test property + lines_availability_test = self.env["pms.folio.availability.wizard"].search( + [ + ("room_type_id.pms_property_ids", "in", self.pms_property1.id), + ] + ) + # set one room type double + value = self.env["pms.num.rooms.selection"].search( + [ + ("room_type_id", "=", self.test_room_type_double.id), + ("value", "=", 1), + ] + ) + lines_availability_test[0].num_rooms_selected = value + + # ACT + booking_engine.create_folio() + + # ASSERT + folio = self.env["pms.folio"].search([("partner_id", "=", self.partner_id.id)]) + + self.assertTrue(folio, "Folio not created.") + + def test_check_create_reservations(self): + """ + Check that reservations are created correctly from the booking engine wizard. + ------------ + The wizard is created with a partner_id, a pricelist, and start and end + dates for property1. The availability items are searched for that property + and in the first one, two double rooms are set, which create two reservations + too. The create_folio() method of the wizard is launched. The folios of the + partner_id entered in the wizard are searched and it is verified that the two + reservations of the folio was created. + """ + + # ARRANGE + + # checkin & checkout + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=1) + + # create folio wizard with partner id => pricelist & start-end dates + booking_engine = self.env["pms.booking.engine"].create( + { + "start_date": checkin, + "end_date": checkout, + "partner_id": self.partner_id.id, + "pricelist_id": self.pricelist1.id, + "pms_property_id": self.pms_property1.id, + "channel_type_id": self.sale_channel_direct1.id, + } + ) + + # availability items belonging to test property + lines_availability_test = self.env["pms.folio.availability.wizard"].search( + [ + ("room_type_id.pms_property_ids", "in", self.pms_property1.id), + ] + ) + # set one room type double + value = self.env["pms.num.rooms.selection"].search( + [ + ("room_type_id", "=", self.test_room_type_double.id), + ("value", "=", 2), + ] + ) + lines_availability_test[0].num_rooms_selected = value + lines_availability_test[0].value_num_rooms_selected = 2 + + # ACT + booking_engine.create_folio() + + folio = self.env["pms.folio"].search([("partner_id", "=", self.partner_id.id)]) + + # ASSERT + self.assertEqual(len(folio.reservation_ids), 2, "Reservations not created.") + + def test_values_folio_created(self): + """ + Check that the partner_id and pricelist_id values of the folio correspond + to the partner_id and pricelist_id of the booking engine wizard that created + the folio. + ----------- + The wizard is created with a partner_id, a pricelist, and start and end + dates for property1. The availability items are searched for that property + and in the first one a double room are set. The create_folio() method of the + wizard is launched. Then it is checked that the partner_id and the pricelist_id + of the created folio are the same as the partner_id and the pricelist_id of the + booking engine wizard. + """ + + # ARRANGE + + # checkin & checkout + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=1) + + # create folio wizard with partner id => pricelist & start-end dates + booking_engine = self.env["pms.booking.engine"].create( + { + "start_date": checkin, + "end_date": checkout, + "partner_id": self.partner_id.id, + "pricelist_id": self.pricelist1.id, + "pms_property_id": self.pms_property1.id, + "channel_type_id": self.sale_channel_direct1.id, + } + ) + # availability items belonging to test property + lines_availability_test = self.env["pms.folio.availability.wizard"].search( + [ + ("room_type_id.pms_property_ids", "in", self.pms_property1.id), + ] + ) + # set one room type double + value = self.env["pms.num.rooms.selection"].search( + [ + ("room_type_id", "=", self.test_room_type_double.id), + ("value", "=", 1), + ] + ) + lines_availability_test[0].num_rooms_selected = value + lines_availability_test[0].value_num_rooms_selected = 1 + + # ACT + booking_engine.create_folio() + vals = { + "partner_id": self.partner_id.id, + "pricelist_id": self.pricelist1.id, + } + folio = self.env["pms.folio"].search([("partner_id", "=", self.partner_id.id)]) + + # ASSERT + for key in vals: + with self.subTest(k=key): + self.assertEqual( + folio[key].id, + vals[key], + "The value of " + key + " is not correctly established", + ) + + def test_values_reservation_created(self): + """ + Check with subtests that the values of the fields of a reservation created through + a booking engine wizard are correct. + -------------- + The wizard is created with a partner_id, a pricelist, and start and end + dates for property1. The availability items are searched for that property + and in the first one a double room are set. The create_folio() method of the + wizard is launched. A vals dictionary is created with folio_id, checkin and + checkout, room_type_id, partner_id, pricelist_id, and pms_property_id. Then + the keys of this dictionary are crossed and it is verified that the values + correspond with the values of the reservation created from the wizard . + """ + + # ARRANGE + + # checkin & checkout + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=1) + + # create folio wizard with partner id => pricelist & start-end dates + booking_engine = self.env["pms.booking.engine"].create( + { + "start_date": checkin, + "end_date": checkout, + "partner_id": self.partner_id.id, + "pricelist_id": self.pricelist1.id, + "pms_property_id": self.pms_property1.id, + "channel_type_id": self.sale_channel_direct1.id, + } + ) + + # availability items belonging to test property + lines_availability_test = self.env["pms.folio.availability.wizard"].search( + [ + ("room_type_id.pms_property_ids", "in", self.pms_property1.id), + ] + ) + # set one room type double + value = self.env["pms.num.rooms.selection"].search( + [ + ("room_type_id", "=", self.test_room_type_double.id), + ("value", "=", 1), + ] + ) + lines_availability_test[0].num_rooms_selected = value + lines_availability_test[0].value_num_rooms_selected = 1 + + # ACT + booking_engine.create_folio() + + folio = self.env["pms.folio"].search([("partner_id", "=", self.partner_id.id)]) + + vals = { + "folio_id": folio.id, + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.test_room_type_double.id, + "partner_id": self.partner_id.id, + "pricelist_id": folio.pricelist_id.id, + "pms_property_id": self.pms_property1.id, + } + + # ASSERT + for reservation in folio.reservation_ids: + for key in vals: + with self.subTest(k=key): + self.assertEqual( + reservation[key].id + if key + in [ + "folio_id", + "partner_id", + "pricelist_id", + "pms_property_id", + "room_type_id", + ] + else reservation[key], + vals[key], + "The value of " + key + " is not correctly established", + ) + + def test_reservation_line_discounts(self): + """ + Check that a discount applied to a reservation from a booking engine wizard + is applied correctly in the reservation line. + ----------------- + The wizard is created with a partner_id, a pricelist, a discount of 0.5 and + start and end dates for property1. The availability items are searched for + that property and in the first one a double room are set. The create_folio() + method of the wizard is launched.Then it is verified that the discount of the + reservation line is equal to the discount applied in the wizard. + """ + + # ARRANGE + + # checkin & checkout + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=1) + discount = 0.5 + + # create folio wizard with partner id => pricelist & start-end dates + booking_engine = self.env["pms.booking.engine"].create( + { + "start_date": checkin, + "end_date": checkout, + "partner_id": self.partner_id.id, + "pricelist_id": self.pricelist1.id, + "discount": discount, + "pms_property_id": self.pms_property1.id, + "channel_type_id": self.sale_channel_direct1.id, + } + ) + # availability items belonging to test property + lines_availability_test = self.env["pms.folio.availability.wizard"].search( + [ + ("room_type_id.pms_property_ids", "in", self.pms_property1.id), + ] + ) + # set one room type double + value = self.env["pms.num.rooms.selection"].search( + [ + ("room_type_id", "=", self.test_room_type_double.id), + ("value", "=", 1), + ] + ) + lines_availability_test[0].num_rooms_selected = value + lines_availability_test[0].value_num_rooms_selected = 1 + + # ACT + booking_engine.create_folio() + + folio = self.env["pms.folio"].search([("partner_id", "=", self.partner_id.id)]) + + # ASSERT + for line in folio.reservation_ids.reservation_line_ids: + with self.subTest(k=line): + self.assertEqual( + line.discount, + discount * 100, + "The discount is not correctly established", + ) + + def test_check_quota_avail(self): + """ + Check that the availability for a room type in the booking engine + wizard is correct by creating an availability_plan_rule with quota. + ----------------- + An availability_plan_rule with quota = 1 is created for the double + room type. A booking engine wizard is created with the checkin same + date as the availability_plan_rule and with pricelist1, which also has + the availability_plan set that contains the availability_plan_rule + created before. Then the availability is searched for the type of + double room which must be 1 because the availavility_plan_rule quota + for that room is 1. + """ + + # ARRANGE + + # checkin & checkout + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=1) + self.availability_plan1 = self.env["pms.availability.plan"].create( + { + "name": "Availability plan for TEST", + "pms_pricelist_ids": [(6, 0, [self.pricelist1.id])], + } + ) + self.env["pms.availability.plan.rule"].create( + { + "quota": 1, + "room_type_id": self.test_room_type_double.id, + "availability_plan_id": self.availability_plan1.id, + "date": fields.date.today(), + "pms_property_id": self.pms_property1.id, + } + ) + # create folio wizard with partner id => pricelist & start-end dates + booking_engine = self.env["pms.booking.engine"].create( + { + "start_date": checkin, + "end_date": checkout, + "partner_id": self.partner_id.id, + "pricelist_id": self.pricelist1.id, + "pms_property_id": self.pms_property1.id, + "channel_type_id": self.sale_channel_direct1.id, + } + ) + room_type_plan_avail = booking_engine.availability_results.filtered( + lambda r: r.room_type_id.id == self.test_room_type_double.id + ).num_rooms_available + + # ASSERT + + self.assertEqual(room_type_plan_avail, 1, "Quota not applied in Wizard Folio") + + def test_check_min_stay_avail(self): + """ + Check that the availability for a room type in the booking engine + wizard is correct by creating an availability_plan_rule with min_stay. + ----------------- + An availability_plan_rule with min_stay = 3 is created for the double + room type. A booking engine wizard is created with start_date = today + and end_date = tomorrow. Then the availability is searched for the type of + double room which must be 0 because the availability_plan_rule establishes + that the min_stay is 3 days and the difference of days in the booking engine + wizard is 1 . + """ + + # ARRANGE + + # checkin & checkout + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=1) + # AVAILABILITY PLAN CREATION + self.availability_plan1 = self.env["pms.availability.plan"].create( + { + "name": "Availability plan for TEST", + "pms_pricelist_ids": [(6, 0, [self.pricelist1.id])], + } + ) + self.env["pms.availability.plan.rule"].create( + { + "min_stay": 3, + "room_type_id": self.test_room_type_double.id, + "availability_plan_id": self.availability_plan1.id, + "date": fields.date.today(), + "pms_property_id": self.pms_property1.id, + } + ) + + # create folio wizard with partner id => pricelist & start-end dates + booking_engine = self.env["pms.booking.engine"].create( + { + "start_date": checkin, + "end_date": checkout, + "partner_id": self.partner_id.id, + "pricelist_id": self.pricelist1.id, + "pms_property_id": self.pms_property1.id, + "channel_type_id": self.sale_channel_direct1.id, + } + ) + room_type_plan_avail = booking_engine.availability_results.filtered( + lambda r: r.room_type_id.id == self.test_room_type_double.id + ).num_rooms_available + + # ASSERT + + self.assertEqual(room_type_plan_avail, 0, "Quota not applied in Wizard Folio") + + @freeze_time("2015-05-05") + def test_price_total_with_board_service(self): + """ + In booking engine when in availability results choose a room or several + and also choose a board service, the total price is calculated from price of the room, + number of nights, board service included price and number of guests + """ + # ARRANGE + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=1) + + self.product_test1 = self.env["product.product"].create( + { + "name": "Test Product 1", + "per_day": True, + "consumed_on": "after", + } + ) + self.board_service_test = self.env["pms.board.service"].create( + { + "name": "Test Board Service", + "default_code": "TBS", + } + ) + self.env["pms.board.service.line"].create( + { + "pms_board_service_id": self.board_service_test.id, + "product_id": self.product_test1.id, + "amount": 8, + "adults": True, + } + ) + self.board_service_room_type = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": self.test_room_type_double.id, + "pms_board_service_id": self.board_service_test.id, + "pms_property_id": self.pms_property1.id, + } + ) + # self.board_service_room_type.flush() + # ACT + booking_engine = self.env["pms.booking.engine"].create( + { + "start_date": checkin, + "end_date": checkout, + "partner_id": self.partner_id.id, + "pricelist_id": self.pricelist1.id, + "pms_property_id": self.pms_property1.id, + "channel_type_id": self.sale_channel_direct1.id, + } + ) + + lines_availability_test = booking_engine.availability_results.filtered( + lambda r: r.room_type_id.id == self.test_room_type_double.id + ) + + value = self.env["pms.num.rooms.selection"].search( + [ + ("room_type_id", "=", self.test_room_type_double.id), + ("value", "=", 1), + ] + ) + lines_availability_test[0].num_rooms_selected = value + lines_availability_test[0].value_num_rooms_selected = 1 + lines_availability_test[ + 0 + ].board_service_room_id = self.board_service_room_type.id + + self.test_room_type_double.list_price = 25 + + room_price = self.test_room_type_double.list_price + days = (checkout - checkin).days + board_service_price = self.board_service_test.amount + room_capacity = self.test_room_type_double.get_room_type_capacity( + self.pms_property1.id + ) + expected_price = room_price * days + ( + board_service_price * room_capacity * days + ) + + # ASSERT + self.assertEqual( + lines_availability_test[0].price_per_room, + expected_price, + "The total price calculation is wrong", + ) + + @freeze_time("2014-05-05") + def _test_board_service_discount(self): + """ + In booking engine when a discount is indicated it must be + applied correctly on both reservation lines and board services, + whether consumed after or before night + """ + # ARRANGE + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=1) + + self.product_test1 = self.env["product.product"].create( + { + "name": "Test Product 1", + "per_day": True, + "consumed_on": "after", + } + ) + self.board_service_test = self.env["pms.board.service"].create( + { + "name": "Test Board Service", + "default_code": "TBS", + } + ) + self.env["pms.board.service.line"].create( + { + "pms_board_service_id": self.board_service_test.id, + "product_id": self.product_test1.id, + "amount": 8, + "adults": True, + } + ) + self.board_service_room_type = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": self.test_room_type_double.id, + "pms_board_service_id": self.board_service_test.id, + "pms_property_id": self.pms_property1.id, + } + ) + discount = 15 + + booking_engine = self.env["pms.booking.engine"].create( + { + "start_date": checkin, + "end_date": checkout, + "partner_id": self.partner_id.id, + "pricelist_id": self.pricelist1.id, + "discount": discount, + "pms_property_id": self.pms_property1.id, + "channel_type_id": self.sale_channel_direct1.id, + } + ) + + lines_availability_test = booking_engine.availability_results.filtered( + lambda r: r.room_type_id.id == self.test_room_type_double.id + ) + value = self.env["pms.num.rooms.selection"].search( + [ + ("room_type_id", "=", self.test_room_type_double.id), + ("value", "=", 1), + ] + ) + lines_availability_test[0].num_rooms_selected = value + lines_availability_test[0].value_num_rooms_selected = 1 + lines_availability_test[ + 0 + ].board_service_room_id = self.board_service_room_type.id + + # ACT + booking_engine.create_folio() + + folio = self.env["pms.folio"].search([("partner_id", "=", self.partner_id.id)]) + # ASSERT + for line in folio.service_ids.service_line_ids: + if line.is_board_service: + self.assertEqual( + line.discount, + discount * 100, + "The discount is not correctly established", + ) + + def test_check_folio_when_change_selection(self): + """ + Check, when creating a folio from booking engine, + if a room type is chosen and then deleted that selection + isn`t registered on the folio and is properly unselected + """ + # ARRANGE + # CREATION OF ROOM TYPE (WITH ROOM TYPE CLASS) + self.partner_id2 = self.env["res.partner"].create( + { + "name": "Brais", + "mobile": "654665553", + "email": "braistest@example.com", + } + ) + self.test_room_type_triple = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id], + "name": "Triple Test", + "default_code": "TRP_Test", + "class_id": self.room_type_class1.id, + "list_price": 60.0, + } + ) + + # pms.room + self.test_room1_triple = self.env["pms.room"].create( + { + "pms_property_id": self.pms_property1.id, + "name": "Triple 301 test", + "room_type_id": self.test_room_type_triple.id, + "capacity": 3, + } + ) + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=1) + + booking_engine = self.env["pms.booking.engine"].create( + { + "start_date": checkin, + "end_date": checkout, + "partner_id": self.partner_id2.id, + "pricelist_id": self.pricelist1.id, + "pms_property_id": self.pms_property1.id, + "channel_type_id": self.sale_channel_direct1.id, + } + ) + + lines_availability_test_double = booking_engine.availability_results.filtered( + lambda r: r.room_type_id.id == self.test_room_type_double.id + ) + value = self.env["pms.num.rooms.selection"].search( + [ + ("room_type_id", "=", self.test_room_type_double.id), + ("value", "=", 1), + ] + ) + lines_availability_test_double[0].num_rooms_selected = value + lines_availability_test_double[0].value_num_rooms_selected = 1 + + lines_availability_test_double[0].value_num_rooms_selected = 0 + + lines_availability_test_triple = booking_engine.availability_results.filtered( + lambda r: r.room_type_id.id == self.test_room_type_triple.id + ) + value_triple = self.env["pms.num.rooms.selection"].search( + [ + ("room_type_id", "=", self.test_room_type_triple.id), + ("value", "=", 1), + ] + ) + lines_availability_test_triple[0].num_rooms_selected = value_triple + lines_availability_test_triple[0].value_num_rooms_selected = 1 + + # ACT + booking_engine.create_folio() + + folio = self.env["pms.folio"].search([("partner_id", "=", self.partner_id2.id)]) + # ASSERT + self.assertEqual( + len(folio.reservation_ids), + 1, + "Reservations of folio are incorrect", + ) + + def _test_adding_board_services_are_saved_on_lines(self): + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=1) + + booking_engine = self.env["pms.booking.engine"].create( + { + "start_date": checkin, + "end_date": checkout, + "partner_id": self.partner_id.id, + "pricelist_id": self.pricelist1.id, + "pms_property_id": self.pms_property1.id, + "channel_type_id": self.sale_channel_direct1.id, + } + ) + booking_engine.availability_results.filtered( + lambda r: r.room_type_id.id == self.test_room_type_double.id + ) + self.assertTrue(False) diff --git a/pms/tests/test_pms_checkin_partner.py b/pms/tests/test_pms_checkin_partner.py new file mode 100644 index 0000000000..6b9fe46541 --- /dev/null +++ b/pms/tests/test_pms_checkin_partner.py @@ -0,0 +1,1657 @@ +import datetime +import logging + +from dateutil.relativedelta import relativedelta +from freezegun import freeze_time + +from odoo import fields +from odoo.exceptions import ValidationError + +from .common import TestPms + +_logger = logging.getLogger(__name__) + + +class TestPmsCheckinPartner(TestPms): + @classmethod + def setUpClass(cls): + super().setUpClass() + today = datetime.date(2012, 1, 14) + cls.room_type1 = cls.env["pms.room.type"].create( + { + "pms_property_ids": [cls.pms_property1.id], + "name": "Triple", + "default_code": "TRP", + "class_id": cls.room_type_class1.id, + } + ) + cls.room1 = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property1.id, + "name": "Triple 101", + "room_type_id": cls.room_type1.id, + "capacity": 3, + } + ) + cls.room1_2 = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property1.id, + "name": "Triple 111", + "room_type_id": cls.room_type1.id, + "capacity": 3, + } + ) + cls.room1_3 = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property1.id, + "name": "Triple 222", + "room_type_id": cls.room_type1.id, + "capacity": 3, + } + ) + + cls.host1 = cls.env["res.partner"].create( + { + "name": "Miguel", + "email": "miguel@example.com", + "birthdate_date": "1995-12-10", + "gender": "male", + } + ) + cls.id_category = cls.env["res.partner.id_category"].search( + [("code", "=", "D")] + ) + if not cls.id_category: + cls.id_category = cls.env["res.partner.id_category"].create( + {"name": "DNI", "code": "D"} + ) + cls.env["res.partner.id_number"].create( + { + "category_id": cls.id_category.id, + "name": "30065089H", + "valid_from": today, + "partner_id": cls.host1.id, + } + ) + cls.sale_channel_direct1 = cls.env["pms.sale.channel"].create( + { + "name": "Door", + "channel_type": "direct", + } + ) + reservation_vals = { + "checkin": today, + "checkout": today + datetime.timedelta(days=3), + "room_type_id": cls.room_type1.id, + "partner_id": cls.host1.id, + "adults": 3, + "pms_property_id": cls.pms_property1.id, + "sale_channel_origin_id": cls.sale_channel_direct1.id, + } + cls.reservation_1 = cls.env["pms.reservation"].create(reservation_vals) + cls.checkin1 = cls.env["pms.checkin.partner"].create( + { + "partner_id": cls.host1.id, + "reservation_id": cls.reservation_1.id, + } + ) + + def test_auto_create_checkins(self): + """ + Check that as many checkin_partners are created as there + adults on the reservation + + Reservation has three adults + """ + + # ACTION + checkins_count = len(self.reservation_1.checkin_partner_ids) + # ASSERT + self.assertEqual( + checkins_count, + 3, + "the automatic partner checkin was not created successful", + ) + + @freeze_time("2012-01-14") + def test_auto_unlink_checkins(self): + # ACTION + host2 = self.env["res.partner"].create( + { + "name": "Carlos", + "mobile": "654667733", + "email": "carlos@example.com", + "birthdate_date": "1995-12-10", + "gender": "male", + } + ) + self.env["res.partner.id_number"].create( + { + "category_id": self.id_category.id, + "name": "85564627G", + "valid_from": datetime.date.today(), + "partner_id": host2.id, + } + ) + self.reservation_1.checkin_partner_ids = [ + ( + 0, + False, + { + "partner_id": host2.id, + }, + ) + ] + + checkins_count = len(self.reservation_1.checkin_partner_ids) + + # ASSERT + self.assertEqual( + checkins_count, + 3, + "the automatic partner checkin was not updated successful", + ) + + def test_onboard_checkin(self): + """ + Check that the reservation cannot be onboard because + checkin_partner data are incomplete and not have onboard status + """ + + # ACT & ASSERT + with self.assertRaises( + ValidationError, msg="Reservation state cannot be 'onboard'" + ): + self.reservation_1.state = "onboard" + + @freeze_time("2012-01-14") + def test_onboard_reservation(self): + """ + Check that reservation state is onboard as the checkin day is + today and checkin_partners data are complete + """ + # ACT + self.checkin1.action_on_board() + + # ASSERT + self.assertEqual( + self.reservation_1.state, + "onboard", + "the reservation checkin was not successful", + ) + + @freeze_time("2012-01-14") + def test_premature_checkin(self): + """ + Check that cannot change checkin_partner state to onboard if + it's not yet checkin day + """ + + # ARRANGE + self.reservation_1.write( + { + "checkin": datetime.date.today() + datetime.timedelta(days=1), + } + ) + # ACT & ASSERT + with self.assertRaises(ValidationError, msg="Cannot do checkin onboard"): + self.checkin1.action_on_board() + + @freeze_time("2012-01-14") + def test_late_checkin_on_checkout_day(self): + """ + Check that allowed register checkin arrival the next day + even if it is the same day of checkout + """ + + # ARRANGE + self.reservation_1.write( + { + "checkin": datetime.date.today() + datetime.timedelta(days=-1), + "checkout": datetime.date.today(), + } + ) + + # ACT + self.checkin1.action_on_board() + + # ASSERT + self.assertEqual( + self.checkin1.arrival, + fields.datetime.now(), + """The system did not allow to check in the next + day because it was the same day of checkout""", + ) + + @freeze_time("2012-01-13") + def test_late_checkin(self): + """ + When host arrives late anad has already passed the checkin day, + the arrival date is updated up to that time. + + In this case checkin day was 2012-01-14 and the host arrived a day later + so the arrival date is updated to that time + + """ + + # ARRANGE + self.reservation_1.write( + { + "checkin": datetime.date.today(), + } + ) + + # ACT + self.checkin1.action_on_board() + + # ASSERT + self.assertEqual( + self.checkin1.arrival, + fields.datetime.now(), + "the late checkin has problems", + ) + + @freeze_time("2012-01-14") + def test_too_many_people_checkin(self): + """ + Reservation cannot have more checkin_partners than adults who have + Reservation has three adults and cannot have four checkin_partner + """ + + # ARRANGE + host2 = self.env["res.partner"].create( + { + "name": "Carlos", + "mobile": "654667733", + "email": "carlos@example.com", + "birthdate_date": "1995-12-10", + "gender": "male", + } + ) + self.env["res.partner.id_number"].create( + { + "category_id": self.id_category.id, + "name": "95876871Z", + "valid_from": datetime.date.today(), + "partner_id": host2.id, + } + ) + host3 = self.env["res.partner"].create( + { + "name": "Enmanuel", + "mobile": "654667733", + "email": "enmanuel@example.com", + "birthdate_date": "1995-12-10", + "gender": "male", + } + ) + self.env["res.partner.id_number"].create( + { + "category_id": self.id_category.id, + "name": "58261664L", + "valid_from": datetime.date.today(), + "partner_id": host3.id, + } + ) + host4 = self.env["res.partner"].create( + { + "name": "Enrique", + "mobile": "654667733", + "email": "enrique@example.com", + "birthdate_date": "1995-12-10", + "gender": "male", + } + ) + self.env["res.partner.id_number"].create( + { + "category_id": self.id_category.id, + "name": "61645604S", + "valid_from": datetime.date.today(), + "partner_id": host4.id, + } + ) + self.env["pms.checkin.partner"].create( + { + "partner_id": host2.id, + "reservation_id": self.reservation_1.id, + } + ) + self.env["pms.checkin.partner"].create( + { + "partner_id": host3.id, + "reservation_id": self.reservation_1.id, + } + ) + # ACT & ASSERT + with self.assertRaises( + ValidationError, + msg="Reservation cannot have more checkin_partner than adults who have", + ): + self.reservation_1.write( + { + "checkin_partner_ids": [ + ( + 0, + 0, + { + "partner_id": host4.id, + }, + ) + ] + } + ) + + @freeze_time("2012-01-14") + def test_count_pending_arrival_persons(self): + """ + After making onboard of two of the three checkin_partners, + one must remain pending arrival, that is a ratio of two thirds + """ + + # ARRANGE + self.host2 = self.env["res.partner"].create( + { + "name": "Carlos", + "mobile": "654667733", + "email": "carlos@example.com", + "birthdate_date": "1995-12-10", + "gender": "male", + } + ) + self.env["res.partner.id_number"].create( + { + "category_id": self.id_category.id, + "name": "63073204M", + "valid_from": datetime.date.today(), + "partner_id": self.host2.id, + } + ) + self.host3 = self.env["res.partner"].create( + { + "name": "Enmanuel", + "mobile": "654667733", + "email": "enmanuel@example.com", + "birthdate_date": "1995-12-10", + "gender": "male", + } + ) + self.env["res.partner.id_number"].create( + { + "category_id": self.id_category.id, + "name": "70699468K", + "valid_from": datetime.date.today(), + "partner_id": self.host3.id, + } + ) + + self.checkin2 = self.env["pms.checkin.partner"].create( + { + "partner_id": self.host2.id, + "reservation_id": self.reservation_1.id, + } + ) + self.checkin3 = self.env["pms.checkin.partner"].create( + { + "partner_id": self.host3.id, + "reservation_id": self.reservation_1.id, + } + ) + + # ACT + self.checkin1.action_on_board() + self.checkin2.action_on_board() + + # ASSERT + self.assertEqual( + self.reservation_1.count_pending_arrival, + 1, + "Fail the count pending arrival on reservation", + ) + self.assertEqual( + self.reservation_1.checkins_ratio, + int(2 * 100 / 3), + "Fail the checkins ratio on reservation", + ) + + def test_complete_checkin_data(self): + """ + Reservation for three adults in a first place has three checkin_partners + pending data. Check that there decrease once their data are entered. + + Reservation has three adults, after entering data of two of them, + check that only one remains to be checked and the ratio of data entered + from checkin_partners is two thirds + """ + + # ARRANGE + self.host2 = self.env["res.partner"].create( + { + "name": "Carlos", + "mobile": "654667733", + "email": "carlos@example.com", + "birthdate_date": "1995-12-10", + "gender": "male", + } + ) + self.env["res.partner.id_number"].create( + { + "category_id": self.id_category.id, + "name": "12650631X", + "valid_from": datetime.date.today(), + "partner_id": self.host2.id, + } + ) + # ACT + + self.checkin2 = self.env["pms.checkin.partner"].create( + { + "partner_id": self.host2.id, + "reservation_id": self.reservation_1.id, + } + ) + pending_checkin_data = self.reservation_1.pending_checkin_data + ratio_checkin_data = self.reservation_1.ratio_checkin_data + # ASSERT + self.assertEqual( + pending_checkin_data, + 1, + "Fail the count pending checkin data on reservation", + ) + self.assertEqual( + ratio_checkin_data, + int(2 * 100 / 3), + "Fail the checkins data ratio on reservation", + ) + + @freeze_time("2012-01-14") + def test_auto_arrival_delayed(self): + """ + The state of reservation 'arrival_delayed' happen when the checkin day + has already passed and the resrvation had not yet changed its state to onboard. + + The date that was previously set was 2012-01-14, + it was advanced two days (to 2012-01-16). + There are three reservations with checkin day on 2012-01-15, + after invoking the method auto_arrival_delayed + those reservation change their state to 'auto_arrival_delayed' + """ + + # ARRANGE + self.host2 = self.env["res.partner"].create( + { + "name": "Carlos", + "mobile": "654667733", + "email": "carlos@example.com", + "birthdate_date": "1995-12-10", + "gender": "male", + } + ) + self.env["res.partner.id_number"].create( + { + "category_id": self.id_category.id, + "name": "61369791H", + "valid_from": datetime.date.today(), + "partner_id": self.host2.id, + } + ) + self.host3 = self.env["res.partner"].create( + { + "name": "Enmanuel", + "mobile": "654667733", + "email": "enmanuel@example.com", + "birthdate_date": "1995-12-10", + "gender": "male", + } + ) + self.env["res.partner.id_number"].create( + { + "category_id": self.id_category.id, + "name": "53563260D", + "valid_from": datetime.date.today(), + "partner_id": self.host3.id, + } + ) + self.host4 = self.env["res.partner"].create( + { + "name": "Enrique", + "mobile": "654667733", + "email": "enrique@example.com", + "birthdate_date": "1995-12-10", + "gender": "male", + } + ) + self.env["res.partner.id_number"].create( + { + "category_id": self.id_category.id, + "name": "63742138F", + "valid_from": datetime.date.today(), + "partner_id": self.host4.id, + } + ) + self.reservation_1.write( + { + "checkin": datetime.date.today() + datetime.timedelta(days=1), + "checkout": datetime.date.today() + datetime.timedelta(days=6), + "adults": 1, + } + ) + reservation2_vals = { + "checkin": datetime.date.today() + datetime.timedelta(days=1), + "checkout": datetime.date.today() + datetime.timedelta(days=6), + "adults": 1, + "room_type_id": self.room_type1.id, + "partner_id": self.host1.id, + "pms_property_id": self.pms_property1.id, + "folio_id": self.reservation_1.folio_id.id, + } + reservation3_vals = { + "checkin": datetime.date.today() + datetime.timedelta(days=1), + "checkout": datetime.date.today() + datetime.timedelta(days=6), + "adults": 1, + "room_type_id": self.room_type1.id, + "partner_id": self.host1.id, + "pms_property_id": self.pms_property1.id, + "folio_id": self.reservation_1.folio_id.id, + } + self.reservation_2 = self.env["pms.reservation"].create(reservation2_vals) + self.reservation_3 = self.env["pms.reservation"].create(reservation3_vals) + folio_1 = self.reservation_1.folio_id + PmsReservation = self.env["pms.reservation"] + + # ACTION + freezer = freeze_time("2012-01-16 10:00:00") + freezer.start() + PmsReservation.auto_arrival_delayed() + + arrival_delayed_reservations = folio_1.reservation_ids.filtered( + lambda r: r.state == "arrival_delayed" + ) + + # ASSERT + self.assertEqual( + len(arrival_delayed_reservations), + 3, + "Reservations not set like No Show", + ) + freezer.stop() + + @freeze_time("2012-01-14") + def test_auto_arrival_delayed_checkout(self): + """ + The state of reservation 'arrival_delayed' happen when the checkin day + has already passed and the reservation had not yet changed its state to onboard. + But, if checkout day is passed without checkout, the reservation pass to + departure delayed with a reservation note warning + + The date that was previously set was 2012-01-14, + it was advanced two days (to 2012-01-16). + There are three reservations with checkout day on 2012-01-15, + after invoking the method auto_arrival_delayed + those reservation change their state to 'departure_delayed' + """ + + # ARRANGE + self.host2 = self.env["res.partner"].create( + { + "name": "Carlos", + "mobile": "654667733", + "email": "carlos@example.com", + "birthdate_date": "1995-12-10", + "gender": "male", + } + ) + self.env["res.partner.id_number"].create( + { + "category_id": self.id_category.id, + "name": "61369791H", + "valid_from": datetime.date.today(), + "partner_id": self.host2.id, + } + ) + self.host3 = self.env["res.partner"].create( + { + "name": "Enmanuel", + "mobile": "654667733", + "email": "enmanuel@example.com", + "birthdate_date": "1995-12-10", + "gender": "male", + } + ) + self.env["res.partner.id_number"].create( + { + "category_id": self.id_category.id, + "name": "53563260D", + "valid_from": datetime.date.today(), + "partner_id": self.host3.id, + } + ) + self.host4 = self.env["res.partner"].create( + { + "name": "Enrique", + "mobile": "654667733", + "email": "enrique@example.com", + "birthdate_date": "1995-12-10", + "gender": "male", + } + ) + self.env["res.partner.id_number"].create( + { + "category_id": self.id_category.id, + "name": "63742138F", + "valid_from": datetime.date.today(), + "partner_id": self.host4.id, + } + ) + self.reservation_1.write( + { + "checkin": datetime.date.today(), + "checkout": datetime.date.today() + datetime.timedelta(days=1), + "adults": 1, + } + ) + reservation2_vals = { + "checkin": datetime.date.today(), + "checkout": datetime.date.today() + datetime.timedelta(days=1), + "adults": 1, + "room_type_id": self.room_type1.id, + "partner_id": self.host1.id, + "pms_property_id": self.pms_property1.id, + "folio_id": self.reservation_1.folio_id.id, + } + reservation3_vals = { + "checkin": datetime.date.today(), + "checkout": datetime.date.today() + datetime.timedelta(days=1), + "adults": 1, + "room_type_id": self.room_type1.id, + "partner_id": self.host1.id, + "pms_property_id": self.pms_property1.id, + "folio_id": self.reservation_1.folio_id.id, + } + self.reservation_2 = self.env["pms.reservation"].create(reservation2_vals) + self.reservation_3 = self.env["pms.reservation"].create(reservation3_vals) + folio_1 = self.reservation_1.folio_id + PmsReservation = self.env["pms.reservation"] + + # ACTION + freezer = freeze_time("2012-01-16 10:00:00") + freezer.start() + PmsReservation.auto_arrival_delayed() + + departure_delayed_reservations = folio_1.reservation_ids.filtered( + lambda r: r.state == "arrival_delayed" + ) + # ASSERT + self.assertEqual( + len(departure_delayed_reservations), + 3, + "Reservations not set like No Show", + ) + freezer.stop() + + @freeze_time("2012-01-14") + def test_auto_departure_delayed(self): + """ + When it's checkout dat and the reservation + was in 'onboard' state, that state change to + 'departure_delayed' if the manual checkout wasn't performed. + + The date that was previously set was 2012-01-14, + it was advanced two days (to 2012-01-17). + Reservation1 has checkout day on 2012-01-17, + after invoking the method auto_departure_delayed + this reservation change their state to 'auto_departure_delayed' + """ + + # ARRANGE + self.reservation_1.write( + { + "checkin": datetime.date.today(), + "checkout": datetime.date.today() + datetime.timedelta(days=3), + "adults": 1, + } + ) + PmsReservation = self.env["pms.reservation"] + self.checkin1.action_on_board() + + # ACTION + freezer = freeze_time("2012-01-17 12:00:00") + freezer.start() + PmsReservation.auto_departure_delayed() + + freezer.stop() + # ASSERT + self.assertEqual( + self.reservation_1.state, + "departure_delayed", + "Reservations not set like Departure delayed", + ) + + # REVIEW: Redesing constrains mobile&mail control + # @freeze_time("2010-12-10") + # def test_not_valid_emails(self): + # # TEST CASES + # # Check that the email format is incorrect + + # # ARRANGE + # reservation = self.env["pms.reservation"].create( + # { + # "checkin": datetime.date.today(), + # "checkout": datetime.date.today() + datetime.timedelta(days=3), + # "room_type_id": self.room_type1.id, + # "partner_id": self.env.ref("base.res_partner_12").id, + # "adults": 3, + # "pms_property_id": self.pms_property1.id, + # } + # ) + # test_cases = [ + # "myemail", + # "myemail@", + # "myemail@", + # "myemail@.com", + # ".myemail", + # ".myemail@", + # ".myemail@.com" ".myemail@.com." "123myemail@aaa.com", + # ] + # for mail in test_cases: + # with self.subTest(i=mail): + # with self.assertRaises( + # ValidationError, msg="Email format is correct and shouldn't" + # ): + # reservation.write( + # { + # "checkin_partner_ids": [ + # ( + # 0, + # False, + # { + # "name": "Carlos", + # "email": mail, + # }, + # ) + # ] + # } + # ) + + # @freeze_time("2014-12-10") + # def test_valid_emails(self): + # # TEST CASES + # # Check that the email format is correct + + # # ARRANGE + # reservation = self.env["pms.reservation"].create( + # { + # "checkin": datetime.date.today(), + # "checkout": datetime.date.today() + datetime.timedelta(days=4), + # "room_type_id": self.room_type1.id, + # "partner_id": self.env.ref("base.res_partner_12").id, + # "adults": 3, + # "pms_property_id": self.pms_property1.id, + # } + # ) + # test_cases = [ + # "hello@commitsun.com", + # "hi.welcome@commitsun.com", + # "hi.welcome@dev.commitsun.com", + # "hi.welcome@dev-commitsun.com", + # "john.doe@xxx.yyy.zzz", + # ] + # for mail in test_cases: + # with self.subTest(i=mail): + # guest = self.env["pms.checkin.partner"].create( + # { + # "name": "Carlos", + # "email": mail, + # "reservation_id": reservation.id, + # } + # ) + # self.assertEqual( + # mail, + # guest.email, + # ) + # guest.unlink() + + # @freeze_time("2016-12-10") + # def test_not_valid_phone(self): + # # TEST CASES + # # Check that the phone format is incorrect + + # # ARRANGE + # reservation = self.env["pms.reservation"].create( + # { + # "checkin": datetime.date.today(), + # "checkout": datetime.date.today() + datetime.timedelta(days=1), + # "room_type_id": self.room_type1.id, + # "partner_id": self.env.ref("base.res_partner_12").id, + # "adults": 3, + # "pms_property_id": self.pms_property1.id, + # } + # ) + # test_cases = [ + # "phone", + # "123456789123", + # "123.456.789", + # "123", + # "123123", + # ] + # for phone in test_cases: + # with self.subTest(i=phone): + # with self.assertRaises( + # ValidationError, msg="Phone format is correct and shouldn't" + # ): + # self.env["pms.checkin.partner"].create( + # { + # "name": "Carlos", + # "mobile": phone, + # "reservation_id": reservation.id, + # } + # ) + + # @freeze_time("2018-12-10") + # def test_valid_phones(self): + # # TEST CASES + # # Check that the phone format is correct + + # # ARRANGE + # reservation = self.env["pms.reservation"].create( + # { + # "checkin": datetime.date.today(), + # "checkout": datetime.date.today() + datetime.timedelta(days=5), + # "room_type_id": self.room_type1.id, + # "partner_id": self.env.ref("base.res_partner_12").id, + # "adults": 3, + # "pms_property_id": self.pms_property1.id, + # } + # ) + # test_cases = [ + # "981 981 981", + # "981981981", + # "981 98 98 98", + # ] + # for mobile in test_cases: + # with self.subTest(i=mobile): + # guest = self.env["pms.checkin.partner"].create( + # { + # "name": "Carlos", + # "mobile": mobile, + # "reservation_id": reservation.id, + # } + # ) + # self.assertEqual( + # mobile, + # guest.mobile, + # ) + + def test_complete_checkin_data_with_partner_data(self): + """ + When a partner is asociated with a checkin, checkin data + will be equal to the partner data + + Host1: + "email": "miguel@example.com", + "birthdate_date": "1995-12-10", + "gender": "male", + + Checkin1: + "partner_id": host1.id + + So after this: + Checkin1: + "email": "miguel@example.com", + "birthdate_date": "1995-12-10", + "gender": "male", + """ + # ARRANGE + partner_data = [self.host1.birthdate_date, self.host1.email, self.host1.gender] + checkin_data = [ + self.checkin1.birthdate_date, + self.checkin1.email, + self.checkin1.gender, + ] + + # ASSERT + for i in [0, 1, 2]: + self.assertEqual( + partner_data[i], + checkin_data[i], + "Checkin data must be the same as partner data ", + ) + + def test_create_partner_when_checkin_has_enought_data(self): + """ + Check that partner is created when the necessary minimum data is entered + into checkin_partner data + """ + # ACT & ASSERT + checkin = self.env["pms.checkin.partner"].create( + { + "firstname": "Pepe", + "lastname": "Paz", + "document_type": self.id_category.id, + "document_number": "77156490T", + "reservation_id": self.reservation_1.id, + } + ) + + # ASSERT + self.assertTrue( + checkin.partner_id, + "Partner should have been created and associated with the checkin", + ) + + def test_not_create_partner_checkin_hasnt_enought_data(self): + """ + Check that partner is not created when the necessary minimum data isn't entered + into checkin_partner data, in this case document_id and document_number + """ + # ACT & ASSERT + checkin = self.env["pms.checkin.partner"].create( + { + "firstname": "Pepe", + "lastname": "Paz", + "email": "pepepaz@gmail.com", + "mobile": "666777777", + "reservation_id": self.reservation_1.id, + } + ) + + # ASSERT + self.assertFalse( + checkin.partner_id, + "Partner mustn't have been created and associated with the checkin", + ) + + def test_add_partner_data_from_checkin(self): + """ + If the checkin_partner has some data that the partner doesn't have, + these are saved in the partner + + In this case, host1 hasn't mobile but the checkin_partner associated with it does, + so the mobile of checkin_partner is added to the partner data + + Note that if the mobile is entered before partnee was associated, this or other fields + are overwritten by the partner's fields. In this case it is entered once the partner has + already been associated + """ + # ARRANGE + self.checkin1.mobile = "666777888" + # ASSERT + self.assertTrue(self.host1.mobile, "Partner mobile must be added") + + def test_partner_id_numbers_created_from_checkin(self): + """ + Some of the required data of the checkin_partner to create the partner are document_type + and document_number, with them an id_number is created associated with the partner that + has just been created. + In this test it is verified that this document has been created correctly + """ + # ACT & ARRANGE + checkin = self.env["pms.checkin.partner"].create( + { + "firstname": "Pepe", + "lastname": "Paz", + "document_type": self.id_category.id, + "document_number": "77156490T", + "reservation_id": self.reservation_1.id, + } + ) + + checkin.flush() + + # ASSERT + self.assertTrue( + checkin.partner_id.id_numbers, + "Partner id_number should have been created and hasn't been", + ) + + def _test_partner_not_modified_when_checkin_modified(self): + """ + If a partner is associated with a checkin + and some of their data is modified in the checkin, + they will not be modified in the partner + """ + # ARRANGE + self.checkin1.email = "prueba@gmail.com" + + # ASSERT + self.assertNotEqual( + self.host1.email, + self.checkin1.email, + "Checkin partner email and partner email shouldn't match", + ) + + def test_partner_modified_previous_checkin_not_modified(self): + """ + If a partner modifies any of its fields, these change mustn't be reflected + in the previous checkins associated with it + """ + # ARRANGE + self.checkin1.flush() + self.host1.gender = "female" + # ASSERT + self.assertNotEqual( + self.host1.gender, + self.checkin1.gender, + "Checkin partner gender and partner gender shouldn't match", + ) + + def test_add_partner_if_exists_from_checkin(self): + """ + Check when a document_type and document_number are entered in a checkin if this + document already existes and is associated with a partner, this partner will be + associated with the checkin + """ + # ACT + host = self.env["res.partner"].create( + { + "name": "Ricardo", + "mobile": "666555666", + "email": "ricardo@example.com", + "birthdate_date": "1995-11-14", + "gender": "male", + } + ) + + self.env["res.partner.id_number"].create( + { + "category_id": self.id_category.id, + "name": "55562998N", + "partner_id": host.id, + } + ) + + # ARRANGE + checkin = self.env["pms.checkin.partner"].create( + { + "document_type": self.id_category.id, + "document_number": "55562998N", + "reservation_id": self.reservation_1.id, + } + ) + + # ASSERT + self.assertEqual( + checkin.partner_id.id, + host.id, + "Checkin partner_id must be the same as the one who has that document", + ) + + def test_is_possible_customer_by_email(self): + """ + It is checked that the field possible_existing_customer_ids + exists in a checkin partner with an email from a res.partner saved + in the DB. + ---------------- + A res.partner is created with the name and email fields. A checkin partner + is created by adding the same email as the res.partner. Then it is + checked that some possible_existing_customer_ids exists. + """ + # ARRANGE + partner = self.env["res.partner"].create( + { + "name": "Courtney Campbell", + "email": "courtney@example.com", + } + ) + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=3) + reservation = self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type1.id, + "pms_property_id": self.pms_property1.id, + "partner_name": partner.name, + "email": partner.email, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + # ACT + checkin = self.env["pms.checkin.partner"].create( + { + "name": partner.name, + "email": partner.email, + "reservation_id": reservation.id, + } + ) + # ASSERT + self.assertTrue( + checkin.possible_existing_customer_ids, + "No customer found with this email", + ) + + def test_is_possible_customer_by_mobile(self): + """ + It is checked that the field possible_existing_customer_ids + exists in a checkin partner with a mobile from a res.partner saved + in the DB. + ---------------- + A res.partner is created with the name and email fields. A checkin partner + is created by adding the same mobile as the res.partner. Then it is + checked that some possible_existing_customer_ids exists. + """ + # ARRANGE + partner = self.env["res.partner"].create( + { + "name": "Ledicia Sandoval", + "mobile": "615369231", + } + ) + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=3) + reservation = self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type1.id, + "pms_property_id": self.pms_property1.id, + "partner_name": partner.name, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + # ACT + checkin = self.env["pms.checkin.partner"].create( + { + "name": partner.name, + "mobile": partner.mobile, + "reservation_id": reservation.id, + } + ) + # ASSERT + self.assertTrue( + checkin.possible_existing_customer_ids, + "No customer found with this mobile", + ) + + def test_add_possible_customer(self): + """ + Check that a partner was correctly added to the checkin partner + after launching the add_partner() method of the several partners wizard + --------------- + A res.partner is created with name, email and mobile. A checkin partner is + created with the email field equal to that of the res.partner created before. + The wizard is created with the checkin partner id and the partner added to the + possible_existing_customer_ids field. The add_partner method of the wizard + is launched and it is checked that the partner was correctly added to the + checkin partner. + """ + # ARRANGE + partner = self.env["res.partner"].create( + { + "name": "Serafín Rivas", + "email": "serafin@example.com", + "mobile": "60595595", + } + ) + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=3) + reservation = self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type1.id, + "pms_property_id": self.pms_property1.id, + "partner_name": partner.name, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + checkin = self.env["pms.checkin.partner"].create( + { + "name": partner.name, + "email": partner.email, + "reservation_id": reservation.id, + } + ) + + several_partners_wizard = self.env["pms.several.partners.wizard"].create( + { + "checkin_partner_id": checkin.id, + "possible_existing_customer_ids": [(6, 0, [partner.id])], + } + ) + # ACT + several_partners_wizard.add_partner() + # ASSERT + self.assertEqual( + checkin.partner_id.id, + partner.id, + "The partner was not added to the checkin partner ", + ) + + def test_not_add_several_possibles_customers(self): + """ + Check that multiple partners cannot be added to a checkin partner + from the several partners wizard. + --------------- + Two res.partner are created with name, email and mobile. A checkin partner is + created with the email field equal to that of the partner1 created before. + The wizard is created with the checkin partner id and the two partners added to the + possible_existing_customer_ids field. The add_partner method of the wizard + is launched and it is verified that a Validation_Error was raised. + """ + # ARRANGE + partner1 = self.env["res.partner"].create( + { + "name": "Serafín Rivas", + "email": "serafin@example.com", + "mobile": "60595595", + } + ) + partner2 = self.env["res.partner"].create( + { + "name": "Simon", + "mobile": "654667733", + "email": "simon@example.com", + } + ) + + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=3) + reservation = self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type1.id, + "pms_property_id": self.pms_property1.id, + "partner_name": partner1.name, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + checkin = self.env["pms.checkin.partner"].create( + { + "name": partner1.name, + "email": partner1.email, + "reservation_id": reservation.id, + } + ) + + several_partners_wizard = self.env["pms.several.partners.wizard"].create( + { + "checkin_partner_id": checkin.id, + "possible_existing_customer_ids": [(6, 0, [partner1.id, partner2.id])], + } + ) + + # ACT AND ASSERT + with self.assertRaises( + ValidationError, + msg="Two partners cannot be added to the checkin partner", + ): + several_partners_wizard.add_partner() + + def test_not_add_any_possibles_customers(self): + """ + Check that the possible_existing_customer_ids field of the several + partners wizard can be left empty and then launch the add_partner() + method of this wizard to add a partner in checkin_partner. + --------------- + A checkin_partner is created. The wizard is created without the + possible_existing_customer_ids field. The add_partner method of + the wizard is launched and it is verified that a Validation_Error + was raised. + """ + + # ARRANGE + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=3) + reservation = self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type1.id, + "pms_property_id": self.pms_property1.id, + "partner_name": "Rosa Costa", + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + checkin = self.env["pms.checkin.partner"].create( + {"name": "Rosa Costa", "reservation_id": reservation.id} + ) + + several_partners_wizard = self.env["pms.several.partners.wizard"].create( + { + "checkin_partner_id": checkin.id, + } + ) + + # ACT AND ASSERT + with self.assertRaises( + ValidationError, + msg="A partner can be added to the checkin partner", + ): + several_partners_wizard.add_partner() + + def test_calculate_dni_expedition_date_from_validity_date_age_lt_30(self): + """ + Check that the calculate_doc_type_expedition_date_from_validity_date() + method calculates correctly the expedition_date of an id category DNI + when the age is less than 30. + ------------- + We launch the method calculate_doc_type_expedition_date_from_validity_date + with the parameters doc_type_id DNI, birthdate calculated so that the age + is = 20 years old and document_date = today + 1 year. The expected + expedition date has to be doc_date - 5 years + """ + doc_date = fields.date.today() + relativedelta(years=1) + doc_date_str = str(doc_date) + + # age=20 years old + birthdate = fields.date.today() - relativedelta(years=20) + birthdate_str = str(birthdate) + + # expected_expedition_date = doc_date - 5 years + expected_exp_date = doc_date - relativedelta(years=5) + expedition_date = ( + self.checkin1.calculate_doc_type_expedition_date_from_validity_date( + self.id_category, doc_date_str, birthdate_str + ) + ) + date_expedition_date = datetime.date( + year=expedition_date.year, + month=expedition_date.month, + day=expedition_date.day, + ) + self.assertEqual( + date_expedition_date, + expected_exp_date, + "Expedition date doesn't correspond with expected expedition date", + ) + + def test_calculate_dni_expedition_date_from_validity_date_age_gt_30(self): + """ + Check that the calculate_doc_type_expedition_date_from_validity_date() + method calculates correctly the expedition_date of an id category DNI + when the age is greater than 30. + ------------- + We launch the method calculate_doc_type_expedition_date_from_validity_date + with the parameters doc_type_id DNI, birthdate calculated so that the age + is = 40 years old and document_date = today + 1 year. The expected + expedition date has to be doc_date - 10 years + """ + doc_date = fields.date.today() + relativedelta(years=1) + doc_date_str = str(doc_date) + + # age=40 years old + birthdate = fields.date.today() - relativedelta(years=40) + birthdate_str = str(birthdate) + + # expected_expedition_date = doc_date - 10 years + expected_exp_date = doc_date - relativedelta(years=10) + expedition_date = ( + self.checkin1.calculate_doc_type_expedition_date_from_validity_date( + self.id_category, doc_date_str, birthdate_str + ) + ) + date_expedition_date = datetime.date( + year=expedition_date.year, + month=expedition_date.month, + day=expedition_date.day, + ) + self.assertEqual( + date_expedition_date, + expected_exp_date, + "Expedition date doesn't correspond with expected expedition date", + ) + + def test_calculate_passport_expedition_date_from_validity_date_age_lt_30(self): + """ + Check that the calculate_doc_type_expedition_date_from_validity_date() + method calculates correctly the expedition_date of an id category Passport + when the age is less than 30. + ------------- + We launch the method calculate_doc_type_expedition_date_from_validity_date + with the parameters doc_type_id Passport, birthdate calculated so that the age + is = 20 years old and document_date = today + 1 year. The expected + expedition date has to be doc_date - 5 years + """ + doc_date = fields.date.today() + relativedelta(years=1) + doc_date_str = str(doc_date) + + # age=20 years old + birthdate = fields.date.today() - relativedelta(years=20) + birthdate_str = str(birthdate) + + # expected_expedition_date = doc_date - 5 years + expected_exp_date = doc_date - relativedelta(years=5) + expedition_date = ( + self.checkin1.calculate_doc_type_expedition_date_from_validity_date( + self.id_category, doc_date_str, birthdate_str + ) + ) + date_expedition_date = datetime.date( + year=expedition_date.year, + month=expedition_date.month, + day=expedition_date.day, + ) + self.assertEqual( + date_expedition_date, + expected_exp_date, + "Expedition date doesn't correspond with expected expedition date", + ) + + def test_calculate_passport_expedition_date_from_validity_date_age_gt_30(self): + """ + Check that the calculate_doc_type_expedition_date_from_validity_date() + method calculates correctly the expedition_date of an id category Passport + when the age is greater than 30. + ------------- + We launch the method calculate_doc_type_expedition_date_from_validity_date + with the parameters doc_type_id Passport, birthdate calculated so that the age + is = 40 years old and document_date = today + 1 year. The expected + expedition date has to be doc_date - 10 years + """ + doc_type_id = self.env["res.partner.id_category"].search([("code", "=", "P")]) + doc_date = fields.date.today() + relativedelta(years=1) + doc_date_str = str(doc_date) + + # age=40 years old + birthdate = fields.date.today() - relativedelta(years=40) + birthdate_str = str(birthdate) + + # expected_expedition_date = doc_date - 10 years + expected_exp_date = doc_date - relativedelta(years=10) + expedition_date = ( + self.checkin1.calculate_doc_type_expedition_date_from_validity_date( + doc_type_id, doc_date_str, birthdate_str + ) + ) + date_expedition_date = datetime.date( + year=expedition_date.year, + month=expedition_date.month, + day=expedition_date.day, + ) + self.assertEqual( + date_expedition_date, + expected_exp_date, + "Expedition date doesn't correspond with expected expedition date", + ) + + def test_calculate_drive_license_expedition_date_from_validity_date_age_lt_70(self): + """ + Check that the calculate_doc_type_expedition_date_from_validity_date() + method calculates correctly the expedition_date of an id category Driving + License when the age is lesser than 70. + ------------- + We launch the method calculate_doc_type_expedition_date_from_validity_date + with the parameters doc_type_id DNI, birthdate calculated so that the age + is = 40 years old and document_date = today + 1 year. The expected + expedition date has to be doc_date - 10 years + """ + doc_type_id = self.env["res.partner.id_category"].search([("code", "=", "C")]) + doc_date = fields.date.today() + relativedelta(years=1) + doc_date_str = str(doc_date) + + # age=40 years old + birthdate = fields.date.today() - relativedelta(years=40) + birthdate_str = str(birthdate) + + # expected_expedition_date = doc_date - 10 years + expected_exp_date = doc_date - relativedelta(years=10) + expedition_date = ( + self.checkin1.calculate_doc_type_expedition_date_from_validity_date( + doc_type_id, doc_date_str, birthdate_str + ) + ) + date_expedition_date = datetime.date( + year=expedition_date.year, + month=expedition_date.month, + day=expedition_date.day, + ) + self.assertEqual( + date_expedition_date, + expected_exp_date, + "Expedition date doesn't correspond with expected expedition date", + ) + + def test_calculate_expedition_date(self): + """ + Check that if the value of the doc_date is less than today, + the method calculate_doc_type_expedition_date_from_validity_date + returns the value of the doc_date as expedition_date. + ----------- + We launch the method calculate_doc_type_expedition_date_from_validity_date + with the parameters doc_type_id DNI, birthdate calculated so that the age + is = 20 years old and document_date = today - 1 year. The expected + expedition date has to be the value of doc_date. + """ + doc_type_id = self.env["res.partner.id_category"].search([("code", "=", "D")]) + doc_date = fields.date.today() - relativedelta(years=1) + doc_date_str = str(doc_date) + birthdate = fields.date.today() - relativedelta(years=20) + birthdate_str = str(birthdate) + expedition_date = ( + self.checkin1.calculate_doc_type_expedition_date_from_validity_date( + doc_type_id, doc_date_str, birthdate_str + ) + ) + date_expedition_date = datetime.date( + year=expedition_date.year, + month=expedition_date.month, + day=expedition_date.day, + ) + self.assertEqual( + date_expedition_date, + doc_date, + "Expedition date doesn't correspond with expected expedition date", + ) + + def test_save_checkin_from_portal(self): + """ + Check by subtesting that a checkin partner is saved correctly + with the _save_data_from_portal() method. + --------- + A reservation is created with an adult, and it will create a checkin partner. + A dictionary is created with the values to be saved and with the key 'id' + equal to the id of the checkin_partner created when the reservation was + created. We launch the _save_data_from_portal() method, passing the created + dictionary as a parameter. Then it is verified that the value of each key + in the dictionary corresponds to the fields of the saved checkin_partner. + """ + self.reservation = self.env["pms.reservation"].create( + { + "checkin": datetime.date.today() + datetime.timedelta(days=10), + "checkout": datetime.date.today() + datetime.timedelta(days=13), + "room_type_id": self.room_type1.id, + "partner_id": self.host1.id, + "adults": 1, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + checkin_partner_id = self.reservation.checkin_partner_ids[0] + checkin_partner_vals = { + "checkin_partner": checkin_partner_id, + "id": checkin_partner_id.id, + "firstname": "Serafín", + "lastname": "Rivas", + "lastname2": "Gonzalez", + "document_type": self.id_category, + "document_number": "18038946T", + "document_expedition_date": "07/10/2010", + "birthdate_date": "05/10/1983", + "mobile": "60595595", + "email": "serafin@example.com", + "gender": "male", + "nationality_id": 1, + "residence_state_id": 1, + } + checkin_partner_id._save_data_from_portal(checkin_partner_vals) + checkin_partner_vals.update( + { + "birthdate_date": datetime.date(1983, 10, 5), + "document_expedition_date": datetime.date(2010, 10, 7), + "nationality_id": self.env["res.country"].search([("id", "=", 1)]), + "residence_state_id": self.env["res.country.state"].browse(1), + "document_type": self.id_category, + } + ) + for key in checkin_partner_vals: + with self.subTest(k=key): + self.assertEqual( + self.reservation.checkin_partner_ids[0][key], + checkin_partner_vals[key], + "The value of " + key + " is not correctly established", + ) + + def test_compute_partner_fields(self): + """ + Check that the computes of the checkin_partner fields related to your partner correctly + add these fields to the checkin_partner. + --------------------------------------- + A reservation is created with an adult (checkin_partner) ql which is saved in the + checkin_partner_id variable, a partner is also created with all the fields that are + related to the checkin_partner fields. The partner is added to the partner_id field + of the checkin_partner and, through subtests, it is verified that the fields of the + partner and the associated checkin_partner match. + """ + self.reservation = self.env["pms.reservation"].create( + { + "checkin": datetime.date.today() + datetime.timedelta(days=1), + "checkout": datetime.date.today() + datetime.timedelta(days=2), + "room_type_id": self.room_type1.id, + "partner_id": self.host1.id, + "adults": 1, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + checkin_partner_id = self.reservation.checkin_partner_ids[0] + nationality_id = self.env["res.country"].browse(1) + state_id = self.env["res.country.state"].browse(1) + partner_vals = { + "firstname": "Paz", + "lastname": "Valenzuela", + "lastname2": "Soto", + "email": "paz@example.com", + "birthdate_date": datetime.date(1980, 10, 5), + "gender": "female", + "mobile": "666555444", + "phone": "123456789", + "nationality_id": nationality_id.id, + "residence_street": "Calle 123", + "residence_street2": "Avda. Constitución 123", + "residence_zip": "15700", + "residence_city": "City Residence", + "residence_country_id": nationality_id.id, + "residence_state_id": state_id.id, + # "pms_checkin_partner_ids": checkin_partner_id, + } + self.partner_id = self.env["res.partner"].create(partner_vals) + + partner_vals.update( + { + "nationality_id": nationality_id, + "residence_country_id": nationality_id, + "residence_state_id": state_id, + } + ) + + checkin_partner_id.partner_id = self.partner_id.id + for key in partner_vals: + if key != "pms_checkin_partner_ids": + with self.subTest(k=key): + self.assertEqual( + self.reservation.checkin_partner_ids[0][key], + self.partner_id[key], + "The value of " + key + " is not correctly established", + ) diff --git a/pms/tests/test_pms_folio.py b/pms/tests/test_pms_folio.py new file mode 100644 index 0000000000..1536ed29b7 --- /dev/null +++ b/pms/tests/test_pms_folio.py @@ -0,0 +1,1511 @@ +import datetime + +from freezegun import freeze_time + +from odoo import fields +from odoo.exceptions import ValidationError +from odoo.tests import Form + +from .common import TestPms + + +class TestPmsFolio(TestPms): + + # SetUp and Common Scenarios methods + + @classmethod + def setUpClass(cls): + """ + - common + room_type_double with 2 rooms (double1 and double2) in pms_property1 + """ + super().setUpClass() + + # create room type + cls.room_type_double = cls.env["pms.room.type"].create( + { + "pms_property_ids": [cls.pms_property1.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": cls.room_type_class1.id, + "price": 25, + } + ) + # create room + cls.double1 = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property1.id, + "name": "Double 101", + "room_type_id": cls.room_type_double.id, + "capacity": 2, + } + ) + + # create room + cls.double2 = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property1.id, + "name": "Double 102", + "room_type_id": cls.room_type_double.id, + "capacity": 2, + } + ) + # make current journals payable + journals = cls.env["account.journal"].search( + [ + ("type", "in", ["bank", "cash"]), + ] + ) + journals.allowed_pms_payments = True + + # create sale channel direct + cls.sale_channel_direct1 = cls.env["pms.sale.channel"].create( + { + "name": "Door", + "channel_type": "direct", + } + ) + + def create_sale_channel_scenario(self): + """ + Method to simplified scenario on sale channel tests: + - create a sale_channel1 like indirect + - create a agency1 like sale_channel1 agency + """ + PmsPartner = self.env["res.partner"] + PmsSaleChannel = self.env["pms.sale.channel"] + + self.sale_channel1 = PmsSaleChannel.create( + {"name": "saleChannel1", "channel_type": "indirect"} + ) + self.agency1 = PmsPartner.create( + { + "name": "partner1", + "is_agency": True, + "invoice_to_agency": "always", + "default_commission": 15, + "sale_channel_id": self.sale_channel1.id, + } + ) + + def create_configuration_accounting_scenario(self): + """ + Method to simplified scenario to payments and accounting: + # REVIEW: + - Use new property with odoo demo data company to avoid account configuration + - Emule SetUp with new property: + - create demo_room_type_double + - Create 2 rooms room_type_double + """ + self.pms_property_demo = self.env["pms.property"].create( + { + "name": "Property Based on Comapany Demo", + "company_id": self.env.ref("base.main_company").id, + "default_pricelist_id": self.env.ref("product.list0").id, + } + ) + # create room type + self.demo_room_type_double = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property_demo.id], + "name": "Double Test", + "default_code": "Demo_DBL_Test", + "class_id": self.room_type_class1.id, + "price": 25, + } + ) + # create rooms + self.double1 = self.env["pms.room"].create( + { + "pms_property_id": self.pms_property_demo.id, + "name": "Double 101", + "room_type_id": self.demo_room_type_double.id, + "capacity": 2, + } + ) + self.double2 = self.env["pms.room"].create( + { + "pms_property_id": self.pms_property_demo.id, + "name": "Double 102", + "room_type_id": self.demo_room_type_double.id, + "capacity": 2, + } + ) + + # TestCases: Sale Channels + + def test_default_agency_commission(self): + """ + Check the total commission of a folio with agency based on the + reservation night price and the preconfigured commission in the agency. + ------- + Agency with 15% default commision, folio with one reservation + and 3 nights at 20$ by night (60$ total) + """ + # ARRANGE + self.create_sale_channel_scenario() + # ACT + folio1 = self.env["pms.folio"].create( + { + "agency_id": self.agency1.id, + "pms_property_id": self.pms_property1.id, + } + ) + + self.env["pms.reservation"].create( + { + "folio_id": folio1.id, + "room_type_id": self.room_type_double.id, + "reservation_line_ids": [ + ( + 0, + False, + { + "date": fields.date.today(), + "price": 20, + }, + ), + ( + 0, + False, + { + "date": fields.date.today() + datetime.timedelta(days=1), + "price": 20, + }, + ), + ( + 0, + False, + { + "date": fields.date.today() + datetime.timedelta(days=2), + "price": 20, + }, + ), + ], + } + ) + self.commission = 0 + for reservation in folio1.reservation_ids: + self.commission = ( + self.commission + + reservation.price_total * self.agency1.default_commission / 100 + ) + + # ASSERT + self.assertEqual( + self.commission, folio1.commission, "The folio compute commission is wrong" + ) + + def test_folio_commission(self): + """ + Check commission of a folio with several reservations that have commission + """ + # ARRANGE + self.create_sale_channel_scenario() + + # ACT + folio1 = self.env["pms.folio"].create( + { + "agency_id": self.agency1.id, + "pms_property_id": self.pms_property1.id, + } + ) + + self.env["pms.reservation"].create( + { + "folio_id": folio1.id, + "room_type_id": self.room_type_double.id, + "reservation_line_ids": [ + ( + 0, + False, + { + "date": fields.date.today(), + "price": 20, + }, + ), + ], + } + ) + self.env["pms.reservation"].create( + { + "folio_id": folio1.id, + "room_type_id": self.room_type_double.id, + "reservation_line_ids": [ + ( + 0, + False, + { + "date": fields.date.today(), + "price": 40, + }, + ), + ], + } + ) + + self.commission = 0 + for reservation in folio1.reservation_ids: + if reservation.commission_amount != 0: + self.commission = ( + self.commission + + reservation.price_total * self.agency1.default_commission / 100 + ) + self.folio_commission = folio1.commission + # ASSERT + self.assertEqual( + self.commission, + self.folio_commission, + "The folio compute commission is wrong", + ) + + def test_folio_commission_with_reservations_without_commission(self): + """ + Check commission of a folio with several reservations, + of which the last hasn't commission + + --- folio1: + -reservation1: commission 15% --> commission amount 3.00 + -reservation2: commission 0% --> commission amount 0.00 + + folio1 commission --> 3.00 + """ + # ARRANGE + self.create_sale_channel_scenario() + + # ACT + + folio1 = self.env["pms.folio"].create( + { + "agency_id": self.agency1.id, + "pms_property_id": self.pms_property1.id, + } + ) + + self.env["pms.reservation"].create( + { + "folio_id": folio1.id, + "room_type_id": self.room_type_double.id, + "reservation_line_ids": [ + ( + 0, + False, + { + "date": fields.date.today(), + "price": 20, + }, + ), + ], + } + ) + + self.env["pms.reservation"].create( + { + "folio_id": folio1.id, + "room_type_id": self.room_type_double.id, + "reservation_line_ids": [ + ( + 0, + False, + { + "date": fields.date.today(), + "price": 40, + }, + ), + ], + "commission_percent": 0, + } + ) + self.commission = 0 + for reservation in folio1.reservation_ids: + if reservation.commission_amount != 0: + self.commission = ( + self.commission + + reservation.price_total * self.agency1.default_commission / 100 + ) + self.folio_commission = folio1.commission + # ASSERT + self.assertEqual( + self.commission, + self.folio_commission, + "The folio compute commission is wrong", + ) + + def test_reservation_agency_without_partner(self): + """ + Check that a reservation / folio created with an agency + and without a partner will automatically take the partner. + ------- + Create the folio1 and the reservation1, only set agency_id, + and the partner_id should be the agency itself. + """ + # ARRANGE + self.create_sale_channel_scenario() + + # ACT + folio1 = self.env["pms.folio"].create( + { + "agency_id": self.agency1.id, + "pms_property_id": self.pms_property1.id, + } + ) + + reservation1 = self.env["pms.reservation"].create( + { + "room_type_id": self.room_type_double.id, + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "folio_id": folio1.id, + } + ) + + # ASSERT + self.assertEqual( + reservation1.agency_id, folio1.partner_id, "Agency has to be the partner" + ) + + # TestCases: Priority + + def test_compute_folio_priority(self): + """ + Check the priority of a folio based on its reservations + #TODO: Commented test waiting to redefine the priority calculation + """ + # reservation1 = self.env["pms.reservation"].create( + # { + # "checkin": fields.date.today(), + # "checkout": fields.date.today() + datetime.timedelta(days=1), + # "room_type_id": self.room_type_double.id, + # "partner_id": self.env.ref("base.res_partner_12").id, + # "pms_property_id": self.property.id, + # } + # ) + # reservation1.allowed_checkin = False + + # self.env["pms.reservation"].create( + # { + # "folio_id": reservation1.folio_id.id, + # "checkin": fields.date.today(), + # "checkout": fields.date.today() + datetime.timedelta(days=1), + # "room_type_id": self.room_type_double.id, + # "partner_id": self.env.ref("base.res_partner_12").id, + # "pms_property_id": self.property.id, + # } + # ) + + # self.assertEqual( + # reservation1.priority, + # reservation1.folio_id.max_reservation_priority, + # "The max. reservation priority on the whole folio is incorrect", + # ) + + # TestCases: Payments + @freeze_time("2000-02-02") + def test_full_pay_folio(self): + """ + After making the payment of a folio for the entire amount, + check that there is nothing pending. + ----- + We create a reservation (autocalculates the amounts) and + then make the payment using the do_payment method of the folio, + directly indicating the pending amount on the folio of the newly + created reservation + """ + # ARRANGE + self.create_configuration_accounting_scenario() + reservation1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property_demo.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "partner_id": self.env.ref("base.res_partner_12").id, + "room_type_id": self.demo_room_type_double.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + # ACTION + self.env["pms.folio"].do_payment( + journal=self.env["account.journal"].browse( + reservation1.folio_id.pms_property_id._get_payment_methods().ids[0] + ), + receivable_account=self.env["account.journal"] + .browse(reservation1.folio_id.pms_property_id._get_payment_methods().ids[0]) + .suspense_account_id, + user=self.env.user, + amount=reservation1.folio_id.pending_amount, + folio=reservation1.folio_id, + partner=reservation1.partner_id, + date=fields.date.today(), + ) + + # ASSERT + self.assertFalse( + reservation1.folio_id.pending_amount, + "The pending amount of a folio paid in full has not been zero", + ) + + @freeze_time("2000-02-02") + def test_partial_pay_folio(self): + """ + After making the payment of a folio for the partial amount, + We check that the pending amount is the one that corresponds to it. + ----- + We create a reservation (autocalculates the amounts) and + then make the payment using the do_payment method of the folio, + directly indicating the pending amount on the folio MINUS 1$ + of the newly created reservation + """ + # ARRANGE + self.create_configuration_accounting_scenario() + left_to_pay = 1 + reservation1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property_demo.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "partner_id": self.env.ref("base.res_partner_12").id, + "room_type_id": self.demo_room_type_double.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + # ACTION + self.env["pms.folio"].do_payment( + journal=self.env["account.journal"].browse( + reservation1.folio_id.pms_property_id._get_payment_methods().ids[0] + ), + receivable_account=self.env["account.journal"] + .browse(reservation1.folio_id.pms_property_id._get_payment_methods().ids[0]) + .suspense_account_id, + user=self.env.user, + amount=reservation1.folio_id.pending_amount - left_to_pay, + folio=reservation1.folio_id, + partner=reservation1.partner_id, + date=fields.date.today(), + ) + + # ASSERT + self.assertEqual( + reservation1.folio_id.pending_amount, + left_to_pay, + "The pending amount on a partially paid folio it \ + does not correspond to the amount that it should", + ) + + def test_reservation_type_folio(self): + """ + Check that the reservation_type of a folio with + a reservation with the default reservation_type is equal + to 'normal'. + --------------- + A folio is created. A reservation is created to which the + value of the folio_id is the id of the previously created + folio. Then it is verified that the value of the reservation_type + field of the folio is 'normal'. + """ + # ARRANGE AND ACT + self.partner1 = self.env["res.partner"].create({"name": "Ana"}) + folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_id": self.partner1.id, + } + ) + + self.env["pms.reservation"].create( + { + "room_type_id": self.room_type_double.id, + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "folio_id": folio1.id, + } + ) + + # ASSERT + self.assertEqual( + folio1.reservation_type, + "normal", + "The default reservation type of the folio should be 'normal'", + ) + + def test_invoice_status_staff_reservation(self): + """ + Check that the value of the invoice_status field is 'no' + on a page with reservation_type equal to 'staff'. + ------------ + A reservation is created with the reservation_type field + equal to 'staff'. Then it is verified that the value of + the invoice_status field of the folio created with the + reservation is equal to 'no'. + """ + # ARRANGE + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=3) + self.partner1 = self.env["res.partner"].create({"name": "Pedro"}) + # ACT + reservation = self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "reservation_type": "staff", + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + # ASSERT + self.assertEqual( + reservation.folio_id.invoice_status, + "no", + "The invoice status of the folio in a staff reservation should be 'no' ", + ) + + def test_invoice_status_out_reservation(self): + """ + Check that the value of the invoice_status field is 'no' + on a page with reservation_type equal to 'out'. + ------------ + A reservation is created with the reservation_type field + equal to 'out'. Then it is verified that the value of + the invoice_status field of the folio created with the + reservation is equal to 'no'. + """ + # ARRANGE + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=3) + self.partner1 = self.env["res.partner"].create({"name": "Pedro"}) + closure_reason = self.env["room.closure.reason"].create( + { + "name": "test closure reason", + "description": "test clopsure reason description", + } + ) + # ACT + reservation = self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "reservation_type": "out", + "closure_reason_id": closure_reason.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + # ASSERT + self.assertEqual( + reservation.folio_id.invoice_status, + "no", + "The invoice status of the folio in a out reservation should be 'no' ", + ) + + def test_amount_total_staff_reservation(self): + """ + Check that the amount_total field of the folio whose + reservation has the reservation_type field as staff + is not calculated. + ------------------------- + A folio is created. A reservation is created to which the + value of the folio_id is the id of the previously created + folio and the field reservation_type equal to 'staff'. Then + it is verified that the value of the amount_total field of + the folio is 0. + """ + # ARRANGE + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=3) + self.partner1 = self.env["res.partner"].create({"name": "Pedro"}) + # ACT + folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_id": self.partner1.id, + } + ) + self.env["pms.reservation"].create( + { + "room_type_id": self.room_type_double.id, + "checkin": checkin, + "checkout": checkout, + "folio_id": folio1.id, + "reservation_type": "staff", + } + ) + # ASSERT + self.assertEqual( + folio1.amount_total, + 0.0, + "The amount total of the folio in a staff reservation should be 0", + ) + + def test_amount_total_out_reservation(self): + """ + Check that the amount_total field of the folio whose + reservation has the reservation_type field as out + is not calculated. + ------------------------- + A folio is created. A reservation is created to which the + value of the folio_id is the id of the previously created + folio and the field reservation_type equal to 'out'. Then + it is verified that the value of the amount_total field of + the folio is 0. + """ + # ARRANGE + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=3) + self.partner1 = self.env["res.partner"].create({"name": "Pedro"}) + # ACT + folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_id": self.partner1.id, + } + ) + self.env["pms.reservation"].create( + { + "room_type_id": self.room_type_double.id, + "checkin": checkin, + "checkout": checkout, + "folio_id": folio1.id, + "reservation_type": "out", + } + ) + # ASSERT + self.assertEqual( + folio1.amount_total, + 0.0, + "The amount total of the folio in a out of service reservation should be 0", + ) + + def test_reservation_type_incongruence(self): + """ + Check that a reservation cannot be created + with the reservation_type field different from the + reservation_type of its folio. + ------------- + A folio is created. A reservation is created to which the + value of the folio_id is the id of the previously created + folio and the field reservation_type by default('normal'). + Then it is tried to create another reservation with its + reservation_type equal to 'staff'. But it should throw an + error because the value of the reservation_type of the + folio is equal to 'normal'. + """ + self.partner1 = self.env["res.partner"].create({"name": "Ana"}) + folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_id": self.partner1.id, + } + ) + + self.env["pms.reservation"].create( + { + "room_type_id": self.room_type_double.id, + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=3), + "folio_id": folio1.id, + } + ) + with self.assertRaises( + ValidationError, + msg="You cannot create reservations with different reservation_type for a folio", + ): + self.env["pms.reservation"].create( + { + "room_type_id": self.room_type_double.id, + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=3), + "folio_id": folio1.id, + "reservation_type": "staff", + } + ) + + def _test_create_partner_in_folio(self): + """ + Check that a res_partner is created from a folio. + ------------ + A folio is created by adding the property_id a res.partner + should be created, which is what is checked after creating + the folio. + """ + # ACT + folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_name": "Savannah Byles", + } + ) + # ASSERT + self.assertTrue(folio1.partner_id.id, "The partner has not been created") + + def test_auto_complete_partner_mobile(self): + """ + It is checked that the mobile field of the folio + is correctly added to + a res.partner that exists in + the DB are put in the folio. + -------------------- + A res.partner is created with the name, mobile and email fields. + Then it is checked that the mobile of the res.partner and that of + the folio are the same. + """ + # ARRANGE + partner = self.env["res.partner"].create( + { + "name": "Enrique", + "mobile": "654667733", + "email": "enrique@example.com", + } + ) + self.id_category = self.env["res.partner.id_category"].create( + {"name": "DNI", "code": "D"} + ) + # ACT + folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_id": partner.id, + } + ) + # ASSERT + self.assertEqual( + folio1.mobile, + partner.mobile, + "The partner mobile has not autocomplete in folio", + ) + + def test_auto_complete_partner_email(self): + """ + It is checked that the email field of the folio + is correctly added to + a res.partner that exists in + the DB are put in the folio. + -------------------- + A res.partner is created with the name, mobile and email fields. + Then it is checked that the email of the res.partner and that of + the folio are the same. + """ + # ARRANGE + partner = self.env["res.partner"].create( + { + "name": "Simon", + "mobile": "654667733", + "email": "simon@example.com", + } + ) + self.id_category = self.env["res.partner.id_category"].create( + {"name": "DNI", "code": "D"} + ) + + # ACT + folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_id": partner.id, + } + ) + # ASSERT + self.assertEqual( + folio1.email, + partner.email, + "The partner mobile has not autocomplete in folio", + ) + + def test_is_possible_customer_by_email(self): + """ + It is checked that the field is_possible_existing_customer_id + exists in a folio with an email from a res.partner saved + in the DB. + ---------------- + A res.partner is created with the name and email fields. A folio + is created by adding the same email as the res.partner. Then it is + checked that the field is_possible_existing_customer_id is equal to True. + """ + # ARRANGE + partner = self.env["res.partner"].create( + { + "name": "Courtney Campbell", + "email": "courtney@example.com", + } + ) + # ACT + folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_name": partner.name, + "email": partner.email, + } + ) + # ASSERT + self.assertTrue( + folio1.possible_existing_customer_ids, "No customer found with this email" + ) + + def test_is_possible_customer_by_mobile(self): + """ + It is checked that the field is_possible_existing_customer_id + exists in a folio with a mobile from a res.partner saved + in the DB. + ---------------- + A res.partner is created with the name and email fields. A folio + is created by adding the same mobile as the res.partner. Then it is + checked that the field is_possible_existing_customer_id is equal to True. + """ + # ARRANGE + partner = self.env["res.partner"].create( + { + "name": "Ledicia Sandoval", + "mobile": "615369231", + } + ) + # ACT + folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_name": partner.name, + "mobile": partner.mobile, + } + ) + # ASSERT + self.assertTrue( + folio1.possible_existing_customer_ids, + "No customer found with this mobile", + ) + + def test_add_possible_customer(self): + """ + Check that a partner was correctly added to the folio + after launching the add_partner() method of the several partners wizard + --------------- + A res.partner is created with name, email and mobile. A folio is created. + The wizard is created with the folio id and the partner added to the + possible_existing_customer_ids field. The add_partner method of the wizard + is launched and it is checked that the partner was correctly added to the + folio. + """ + # ARRANGE + partner = self.env["res.partner"].create( + { + "name": "Serafín Rivas", + "email": "serafin@example.com", + "mobile": "60595595", + } + ) + + folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_name": partner.name, + "email": partner.email, + } + ) + + several_partners_wizard = self.env["pms.several.partners.wizard"].create( + { + "folio_id": folio1.id, + "possible_existing_customer_ids": [(6, 0, [partner.id])], + } + ) + # ACT + several_partners_wizard.add_partner() + # ASSERT + self.assertEqual( + folio1.partner_id.id, + partner.id, + "The partner was not added to the folio ", + ) + + def test_not_add_several_possibles_customers(self): + """ + Check that multiple partners cannot be added to a folio + from the several partners wizard. + --------------- + Two res.partner are created with name, email and mobile. A folio is created. + The wizard is created with the folio id and the two partners added to the + possible_existing_customer_ids field. The add_partner method of the wizard + is launched and it is verified that a Validation_Error was raised. + """ + # ARRANGE + partner1 = self.env["res.partner"].create( + { + "name": "Serafín Rivas", + "email": "serafin@example.com", + "mobile": "60595595", + } + ) + partner2 = self.env["res.partner"].create( + { + "name": "Simon", + "mobile": "654667733", + "email": "simon@example.com", + } + ) + + folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_name": partner1.name, + "email": partner1.email, + } + ) + + several_partners_wizard = self.env["pms.several.partners.wizard"].create( + { + "folio_id": folio1.id, + "possible_existing_customer_ids": [(6, 0, [partner1.id, partner2.id])], + } + ) + + # ACT AND ASSERT + with self.assertRaises( + ValidationError, + msg="Two partners cannot be added to the folio", + ): + several_partners_wizard.add_partner() + + def test_not_add_any_possibles_customers(self): + """ + Check that the possible_existing_customer_ids field of the several + partners wizard can be left empty and then launch the add_partner() + method of this wizard to add a partner in folio. + --------------- + A folio is created. The wizard is created without the + possible_existing_customer_ids field. The add_partner method of + the wizard is launched and it is verified that a Validation_Error + was raised. + """ + + # ARRANGE + folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_name": "Rosa Costa", + } + ) + + several_partners_wizard = self.env["pms.several.partners.wizard"].create( + { + "folio_id": folio1.id, + } + ) + + # ACT AND ASSERT + with self.assertRaises( + ValidationError, + msg="A partner can be added to the folio", + ): + several_partners_wizard.add_partner() + + def test_add_partner_invoice_contact(self): + """ + Check that when adding a customer at check-in, reservation or folio, + it is added as a possible billing address + --------------- + Three res.partner are created with name, email and mobile. A folio is created. + We add the partners to the folio, reservation, and checkin, and check that the + three partners are on partner_invoice in folio. + """ + # ARRANGE + partner1 = self.env["res.partner"].create( + { + "name": "Serafín Rivas", + "email": "serafin@example.com", + "mobile": "60595595", + } + ) + partner2 = self.env["res.partner"].create( + { + "name": "Simon", + "mobile": "654667733", + "email": "simon@example.com", + } + ) + partner3 = self.env["res.partner"].create( + { + "name": "Sofia", + "mobile": "688667733", + "email": "sofia@example.com", + } + ) + + # FIRST ACTION + folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_name": partner1.name, + "email": partner1.email, + } + ) + reservation1 = self.env["pms.reservation"].create( + { + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "room_type_id": self.room_type_double.id, + "folio_id": folio1.id, + } + ) + + # FIRST ASSERT + self.assertEqual( + len(folio1.partner_invoice_ids), + 0, + "A partner was added as a billing contact for no reason", + ) + + # SECOND ACTION + folio1.partner_id = partner1.id + + # SECOND ASSERT + self.assertEqual( + folio1.partner_invoice_ids.ids, + [partner1.id], + "A folio partner was not added as a billing contact", + ) + + # SECOND ACTION + reservation1.partner_id = partner2.id + + # SECOND ASSERT + self.assertIn( + partner2.id, + folio1.partner_invoice_ids.ids, + "A reservation partner was not added as a billing contact", + ) + + # THIRD ACTION + reservation1.checkin_partner_ids[0].partner_id = partner3.id + + # THIRD ASSERT + self.assertIn( + partner3.id, + folio1.partner_invoice_ids.ids, + "A checkin partner was not added as a billing contact", + ) + + @freeze_time("2001-10-10") + def test_folio_sale_channel_origin_in_reservation(self): + """ + Check that the reservation has sale_channel_origin_id + as the folio sale_channel_origin_id in + which reservation was created + + When a reservation is created on a folio + that already has a sale_channel_origin + that reservation will have the same sale_channel_origin + + """ + # ARRANGE + partner1 = self.env["res.partner"].create({"name": "partner1"}) + folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_name": partner1.name, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + # ACT + reservation1 = self.env["pms.reservation"].create( + { + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "room_type_id": self.room_type_double.id, + "folio_id": folio1.id, + } + ) + # ASSERT + self.assertEqual( + reservation1.sale_channel_origin_id.id, + folio1.sale_channel_origin_id.id, + "Sale channel of reservation must be the same that it folio", + ) + + @freeze_time("2001-10-19") + def test_folio_sale_channel_ids(self): + """ + Check if sale_channel_ids of folio correspond to + sale_channel_origin_id of its reservations at the + time of creating a new reservation in the folio + """ + # ARRANGE + sale_channel_phone = self.env["pms.sale.channel"].create( + { + "name": "phone", + "channel_type": "direct", + } + ) + partner1 = self.env["res.partner"].create({"name": "partner1"}) + folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_name": partner1.name, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + self.env["pms.reservation"].create( + { + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "room_type_id": self.room_type_double.id, + "folio_id": folio1.id, + } + ) + self.env["pms.reservation"].create( + { + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "room_type_id": self.room_type_double.id, + "folio_id": folio1.id, + "sale_channel_origin_id": sale_channel_phone.id, + } + ) + # ACT + expected_sale_channels = [] + for reservation in folio1.reservation_ids: + expected_sale_channels.append(reservation.sale_channel_origin_id.id) + + # ASSERT + self.assertItemsEqual( + folio1.sale_channel_ids.ids, + list(set(expected_sale_channels)), + "Sale_channel_ids of folio must be the same as " + "sale_channel_origin of its reservation ", + ) + + @freeze_time("2001-10-22") + def test_folio_sale_channel_ids_reservations_several_origin(self): + """ + Check that sale_channel_ids of folio correspond to sale_channel_origin_id + of its reservations + + In this case, folio1 has two reservations(reservation1, reservation2) + with the same sale_channel_origin. + + sale_channel_origin_id sale_channel_ids + ------------------------- + Folio1 --------> sale_channel_direct1 || sale_channel_direct1 + reservation1 --> sale_channel_direct1 + reservation2 --> sale_channel_direct1 + + Then, reservation2 update sale_channel_origin_id for a diferent one. So the folio + has several reservations with different sale_channel_origin_id. + It should be noted that the check would force having to update + the folio sale_channel_origin_id (force_update_origin) isn't marked. + + Expected result: + + sale_channel_origin_id sale_channel_ids + ---------------------- + Folio1 --------> sale_channel_direct1 | (sale_channel_direct1, sale_channel_phone) + reservation1 --> sale_channel_direct1 + reservation2 --> sale_channel_phone + + In this test case, sale_channel_ids will be checked + """ + # ARRANGE + sale_channel_phone = self.env["pms.sale.channel"].create( + { + "name": "phone", + "channel_type": "direct", + } + ) + partner1 = self.env["res.partner"].create({"name": "partner1"}) + folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_name": partner1.name, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + self.env["pms.reservation"].create( + { + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "room_type_id": self.room_type_double.id, + "folio_id": folio1.id, + } + ) + reservation2 = self.env["pms.reservation"].create( + { + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "room_type_id": self.room_type_double.id, + "folio_id": folio1.id, + } + ) + # ACT + reservation_vals = { + "sale_channel_origin_id": sale_channel_phone.id, + "force_update_origin": False, + } + + reservation2.write(reservation_vals) + expected_sale_channels = [] + for reservation in folio1.reservation_ids: + expected_sale_channels.append(reservation.sale_channel_origin_id.id) + + # ASSERT + self.assertItemsEqual( + folio1.sale_channel_ids.ids, + list(set(expected_sale_channels)), + "Sale_channel_ids of folio must be the same as " + "sale_channel_origin of its reservation ", + ) + + @freeze_time("2001-10-22") + def test_sale_channel_origin_id_reservation_not_update_origin(self): + """ + Check that sale_channel_origin_id of folio doesn't change + when sale_channel_origin_id of one of its reservations is updated + but the check isn't checked + + In this case, folio1 has two reservations(reservation1, reservation2) + with the same sale_channel_origin. + + sale_channel_origin_id + ------------------------- + Folio1 --------> sale_channel_direct1 + reservation1 --> sale_channel_direct1 + reservation2 --> sale_channel_direct1 + + Then, reservation2 update sale_channel_origin_id for a diferent one. So the folio + has several reservations with different sale_channel_origin_id. + And the check would force having to update + the folio sale_channel_origin_id (force_update_origin) isn't marked. + So sale_channel_origin_id of folio shouldn't change. + + Expected result: + + sale_channel_origin_id + ------------------------- + Folio1 --------> sale_channel_direct1 + reservation1 --> sale_channel_direct1 + reservation2 --> sale_channel_phone + + In this test case, sale_channel_origin_id of folio will be checked + """ + # ARRANGE + sale_channel_phone = self.env["pms.sale.channel"].create( + { + "name": "phone", + "channel_type": "direct", + } + ) + partner1 = self.env["res.partner"].create({"name": "partner1"}) + folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_name": partner1.name, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + self.env["pms.reservation"].create( + { + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "room_type_id": self.room_type_double.id, + "folio_id": folio1.id, + } + ) + reservation2 = self.env["pms.reservation"].create( + { + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "room_type_id": self.room_type_double.id, + "folio_id": folio1.id, + } + ) + # ACT + reservation_vals = { + "sale_channel_origin_id": sale_channel_phone.id, + "force_update_origin": False, + } + reservation2.write(reservation_vals) + + # ASSERT + self.assertNotEqual( + folio1.sale_channel_origin_id, + reservation2.sale_channel_origin_id, + "Sale_channel_origin_id of folio shouldn't be the same as " + "sale_channel_origin of reservation2", + ) + + @freeze_time("2001-10-25") + def test_sale_channel_origin_id_reservation_update_origin(self): + """ + Check that sale_channel_origin_id of the folio changes when + you change sale_channel_origin_id of one of its reservations + and check that forces the update of sale_channel_origin_id of folio + + + sale_channel_origin_id + ------------------------- + Folio1 --------> sale_channel_direct1 + reservation1 --> sale_channel_direct1 + reservation2 --> sale_channel_direct1 + + Then, reservation2 update sale_channel_origin_id for a diferent one. So the folio + has several reservations with different sale_channel_origin_id. + And the check would force having to update + the folio sale_channel_origin_id (force_update_origin) is marked. + So sale_channel_origin_id of folio must change. + + Expected result: + + sale_channel_origin_id + ------------------------- + Folio1 --------> sale_channel_phone + reservation1 --> sale_channel_phone + reservation2 --> sale_channel_phone + + In this test case, sale_channel_origin_id of folio1 will be checked + """ + # ARRANGE + sale_channel_phone = self.env["pms.sale.channel"].create( + { + "name": "phone", + "channel_type": "direct", + } + ) + partner1 = self.env["res.partner"].create({"name": "partner1"}) + folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_name": partner1.name, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + self.env["pms.reservation"].create( + { + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "room_type_id": self.room_type_double.id, + "folio_id": folio1.id, + } + ) + reservation2 = self.env["pms.reservation"].create( + { + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "room_type_id": self.room_type_double.id, + "folio_id": folio1.id, + } + ) + # ACT + reservation_vals = { + "sale_channel_origin_id": sale_channel_phone.id, + "force_update_origin": True, + } + reservation2.write(reservation_vals) + # ASSERT + self.assertEqual( + folio1.sale_channel_origin_id, + reservation2.sale_channel_origin_id, + "Sale_channel_origin_id of folio should be updated", + ) + + @freeze_time("2001-10-25") + def test_sale_channel_origin_id_reservation_update_reservations(self): + """ + Check that sale_channel_origin_id of a reservation changes when + another reservation of the same folio changes sale_channel_origin_id + and marks the check. + By changing sale_channel_origin_ id of a reservation and marking the check + that forces the update, changes both sale_channel_origin of folio and + sale_channel_origin of reservations that had the same + + + sale_channel_origin_id + ------------------------- + Folio1 --------> sale_channel_direct1 + reservation1 --> sale_channel_direct1 + reservation2 --> sale_channel_direct1 + + Then, reservation2 update sale_channel_origin_id for a diferent one. + And the check would force having to update + the folio sale_channel_origin_id (force_update_origin) is marked. + So sale_channel_origin_id of folio and other reservations with the same + sale_channel_origin must change. + + Expected result: + + sale_channel_origin_id + ------------------------- + Folio1 --------> sale_channel_phone + reservation1 --> sale_channel_phone + reservation2 --> sale_channel_phone + + In this test case, sale_channel_origin_id of reservation1 will be checked + """ + # ARRANGE + sale_channel_phone = self.env["pms.sale.channel"].create( + { + "name": "phone", + "channel_type": "direct", + } + ) + partner1 = self.env["res.partner"].create({"name": "partner1"}) + folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_name": partner1.name, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + reservation1 = self.env["pms.reservation"].create( + { + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "room_type_id": self.room_type_double.id, + "folio_id": folio1.id, + } + ) + reservation2 = self.env["pms.reservation"].create( + { + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "room_type_id": self.room_type_double.id, + "folio_id": folio1.id, + } + ) + # ACT + reservation_vals = { + "sale_channel_origin_id": sale_channel_phone.id, + "force_update_origin": True, + } + reservation2.write(reservation_vals) + + # ASSERT + self.assertEqual( + reservation1.sale_channel_origin_id, + reservation2.sale_channel_origin_id, + "sale_channel_origin_id of reservations that coincided " + "with sale_channel_origin_id of folio de should be updated", + ) + + def test_pms_folio_form_creation(self): + folio_form = Form(self.env["pms.folio"]) + self.assertFalse(folio_form.possible_existing_customer_ids) diff --git a/pms/tests/test_pms_folio_invoice.py b/pms/tests/test_pms_folio_invoice.py new file mode 100644 index 0000000000..ef8387f26e --- /dev/null +++ b/pms/tests/test_pms_folio_invoice.py @@ -0,0 +1,929 @@ +import datetime + +from odoo import fields + +from .common import TestPms + + +class TestPmsFolioInvoice(TestPms): + @classmethod + def setUpClass(cls): + super().setUpClass() + # create a room type availability + cls.room_type_availability = cls.env["pms.availability.plan"].create( + {"name": "Availability plan for TEST"} + ) + + # journal to simplified invoices + cls.simplified_journal = cls.env["account.journal"].create( + { + "name": "Simplified journal", + "code": "SMP", + "type": "sale", + "company_id": cls.env.ref("base.main_company").id, + } + ) + + # create a property + cls.property = cls.env["pms.property"].create( + { + "name": "MY PMS TEST", + "company_id": cls.env.ref("base.main_company").id, + "default_pricelist_id": cls.pricelist1.id, + "journal_simplified_invoice_id": cls.simplified_journal.id, + } + ) + + # create room type + cls.room_type_double = cls.env["pms.room.type"].create( + { + "pms_property_ids": [cls.property.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": cls.room_type_class1.id, + "price": 25, + } + ) + + # create rooms + cls.room1 = cls.env["pms.room"].create( + { + "pms_property_id": cls.property.id, + "name": "Double 101", + "room_type_id": cls.room_type_double.id, + "capacity": 2, + } + ) + + cls.room2 = cls.env["pms.room"].create( + { + "pms_property_id": cls.property.id, + "name": "Double 102", + "room_type_id": cls.room_type_double.id, + "capacity": 2, + } + ) + + cls.room3 = cls.env["pms.room"].create( + { + "pms_property_id": cls.property.id, + "name": "Double 103", + "room_type_id": cls.room_type_double.id, + "capacity": 2, + } + ) + + # res.partner + cls.partner_id = cls.env["res.partner"].create( + { + "name": "Miguel", + "vat": "45224522J", + "country_id": cls.env.ref("base.es").id, + "city": "Madrid", + "zip": "28013", + "street": "Calle de la calle", + } + ) + + # create a sale channel + cls.sale_channel_direct1 = cls.env["pms.sale.channel"].create( + { + "name": "Door", + "channel_type": "direct", + } + ) + + def create_configuration_accounting_scenario(self): + """ + Method to simplified scenario to payments and accounting: + # REVIEW: + - Use new property with odoo demo data company to avoid account configuration + - Emule SetUp with new property: + - create demo_room_type_double + - Create 2 rooms room_type_double + """ + self.pms_property_demo = self.env["pms.property"].create( + { + "name": "Property Based on Comapany Demo", + "company_id": self.env.ref("base.main_company").id, + "default_pricelist_id": self.env.ref("product.list0").id, + } + ) + # create room type + self.demo_room_type_double = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property_demo.id], + "name": "Double Test", + "default_code": "Demo_DBL_Test", + "class_id": self.room_type_class1.id, + "price": 25, + } + ) + # create rooms + self.double1 = self.env["pms.room"].create( + { + "pms_property_id": self.pms_property_demo.id, + "name": "Double 101", + "room_type_id": self.demo_room_type_double.id, + "capacity": 2, + } + ) + self.double2 = self.env["pms.room"].create( + { + "pms_property_id": self.pms_property_demo.id, + "name": "Double 102", + "room_type_id": self.demo_room_type_double.id, + "capacity": 2, + } + ) + # make current journals payable + journals = self.env["account.journal"].search( + [ + ("type", "in", ["bank", "cash"]), + ] + ) + journals.allowed_pms_payments = True + + def _test_invoice_full_folio(self): + """ + Check that when launching the create_invoices() method for a full folio, + the invoice_status field is set to "invoiced". + ---------------- + A reservation is created. The create_invoices() method of the folio of + that reservation is launched. It is verified that the invoice_status field + of the folio is equal to "invoiced". + """ + # ARRANGE + r1 = 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.partner_id.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + state_expected = "invoiced" + # ACT + r1.folio_id._create_invoices() + r1.flush() + # ASSERT + self.assertEqual( + state_expected, + r1.folio_id.invoice_status, + "The status after a full invoice folio isn't correct", + ) + + def _test_invoice_partial_folio_by_steps(self): + """ + Check that when launching the create_invoices() method for a partial folio, + the invoice_status field is set to "invoiced". + ---------------- + A reservation is created. The create_invoices() method of the folio of + that reservation is launched with the first sale line. It is verified + that the invoice_status field of the folio is equal to "invoiced". + """ + # ARRANGE + r1 = 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.partner_id.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + dict_lines = dict() + + dict_lines[ + r1.folio_id.sale_line_ids.filtered(lambda l: not l.display_type)[0].id + ] = 3 + r1.folio_id._create_invoices(lines_to_invoice=dict_lines) + + self.assertEqual( + "invoiced", + r1.folio_id.invoice_status, + "The status after an invoicing is not correct", + ) + + def test_invoice_partial_folio_diferent_partners(self): + # ARRANGE + r1 = 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.partner_id.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + dict_lines = dict() + # qty to 1 to 1st folio sale line + dict_lines[ + r1.folio_id.sale_line_ids.filtered(lambda l: not l.display_type)[0].id + ] = 1 + r1.folio_id._create_invoices( + lines_to_invoice=dict_lines, + partner_invoice_id=self.env.ref("base.res_partner_1").id, + ) + + # test does not work without invalidating cache + self.env["account.move"].invalidate_cache() + + self.assertNotEqual( + "invoiced", + r1.folio_id.invoice_status, + "The status after a partial invoicing is not correct", + ) + # qty to 2 to 1st folio sale line + dict_lines[ + r1.folio_id.sale_line_ids.filtered(lambda l: not l.display_type)[0].id + ] = 2 + r1.folio_id._create_invoices( + lines_to_invoice=dict_lines, + partner_invoice_id=self.env.ref("base.res_partner_12").id, + ) + self.assertNotEqual( + r1.folio_id.move_ids.mapped("partner_id")[0], + r1.folio_id.move_ids.mapped("partner_id")[1], + "The status after an invoicing is not correct", + ) + + def test_amount_invoice_folio(self): + """ + Test create and invoice from the Folio, and check amount of the reservation. + ------------- + A reservation is created. The create_invoices() method is launched and it is + verified that the total amount of the reservation folio is equal to the total + of the created invoice. + """ + r1 = 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.partner_id.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + total_amount_expected = r1.folio_id.amount_total + r1.folio_id._create_invoices() + self.assertEqual( + r1.folio_id.move_ids.amount_total, + total_amount_expected, + "Total amount of the invoice and total amount of folio don't match", + ) + + def test_qty_to_invoice_folio(self): + """ + Test create and invoice from the Folio, and check qty to invoice. + ---------------------- + A reservation is created.Then it is verified that the total quantity + to be invoice from the sale lines of the reservation folio corresponds + to expected. + """ + r1 = 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.partner_id.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + qty_to_invoice_expected = sum( + r1.folio_id.sale_line_ids.mapped("qty_to_invoice") + ) + self.assertEqual( + qty_to_invoice_expected, + 3.0, + "The quantity to be invoice on the folio does not correspond", + ) + + def test_qty_invoiced_folio(self): + """ + Test create and invoice from the Folio, and check qty invoiced. + --------------- + A reservation is created.The create_invoices() method is launched and it is + verified that the total quantity invoiced of the reservation folio is equal + to the total quantity of the created invoice. + """ + r1 = 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.partner_id.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r1.folio_id._create_invoices() + qty_invoiced_expected = sum(r1.folio_id.sale_line_ids.mapped("qty_invoiced")) + self.assertEqual( + qty_invoiced_expected, + 3.0, + "The quantity invoiced on the folio does not correspond", + ) + + def test_price_invoice_by_services_folio(self): + """ + Test create and invoice from the Folio, and check amount in a + specific segment of services. + """ + + self.product1 = self.env["product.product"].create( + {"name": "Test Product 1", "per_day": True, "list_price": 10} + ) + + self.reservation1 = 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.partner_id.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + self.service1 = self.env["pms.service"].create( + { + "is_board_service": False, + "product_id": self.product1.id, + "reservation_id": self.reservation1.id, + } + ) + + dict_lines = dict() + dict_lines[ + self.reservation1.folio_id.sale_line_ids.filtered("service_id")[0].id + ] = 3 + self.reservation1.folio_id._create_invoices(lines_to_invoice=dict_lines) + self.assertEqual( + self.reservation1.folio_id.sale_line_ids.filtered("service_id")[ + 0 + ].price_total, + self.reservation1.folio_id.move_ids.amount_total, + "The service price don't match between folio and invoice", + ) + + def test_qty_invoiced_by_services_folio(self): + """ + Test create and invoice from the Folio, and check qty invoiced + in a specific segment of services + """ + + self.product1 = self.env["product.product"].create( + {"name": "Test Product 1", "per_day": True, "list_price": 10} + ) + + self.reservation1 = 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.partner_id.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + self.service1 = self.env["pms.service"].create( + { + "is_board_service": False, + "product_id": self.product1.id, + "reservation_id": self.reservation1.id, + } + ) + + dict_lines = dict() + service_lines = self.reservation1.folio_id.sale_line_ids.filtered("service_id") + for line in service_lines: + dict_lines[line.id] = 1 + self.reservation1.folio_id._create_invoices(lines_to_invoice=dict_lines) + expected_qty_invoiced = sum( + self.reservation1.folio_id.move_ids.invoice_line_ids.mapped("quantity") + ) + self.assertEqual( + expected_qty_invoiced, + sum(self.reservation1.folio_id.sale_line_ids.mapped("qty_invoiced")), + "The quantity of invoiced services don't match between folio and invoice", + ) + + def test_qty_to_invoice_by_services_folio(self): + """ + Test create an invoice from the Folio, and check qty to invoice + in a specific segment of services + """ + + self.product1 = self.env["product.product"].create( + {"name": "Test Product 1", "per_day": True, "list_price": 10} + ) + + self.reservation1 = 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.partner_id.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + self.service1 = self.env["pms.service"].create( + { + "is_board_service": False, + "product_id": self.product1.id, + "reservation_id": self.reservation1.id, + } + ) + + expected_qty_to_invoice = sum( + self.reservation1.folio_id.sale_line_ids.filtered("service_id").mapped( + "qty_to_invoice" + ) + ) + self.assertEqual( + expected_qty_to_invoice, + 3.0, + "The quantity of services to be invoice is wrong", + ) + + def test_price_invoice_board_service(self): + """ + Test create and invoice from the Folio, and check the related + amounts with board service linked + """ + self.product1 = self.env["product.product"].create( + { + "name": "Test Product 1", + } + ) + + self.board_service1 = self.env["pms.board.service"].create( + { + "name": "Test Board Service 1", + "default_code": "CB1", + } + ) + self.board_service_line1 = self.env["pms.board.service.line"].create( + { + "product_id": self.product1.id, + "pms_board_service_id": self.board_service1.id, + "amount": 10, + "adults": True, + } + ) + + self.board_service_room_type1 = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": self.room_type_double.id, + "pms_board_service_id": self.board_service1.id, + "pms_property_id": self.property.id, + } + ) + self.reservation1 = 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.partner_id.id, + "board_service_room_id": self.board_service_room_type1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + dict_lines = dict() + dict_lines[ + self.reservation1.folio_id.sale_line_ids.filtered("service_id")[0].id + ] = 1 + self.reservation1.folio_id._create_invoices(lines_to_invoice=dict_lines) + self.assertEqual( + self.reservation1.folio_id.sale_line_ids.filtered("service_id")[ + 0 + ].price_total, + self.reservation1.folio_id.move_ids.amount_total, + "The board service price don't match between folio and invoice", + ) + + def test_qty_invoiced_board_service(self): + """ + Test create and invoice from the Folio, and check qty invoiced + with board service linked. + """ + self.product1 = self.env["product.product"].create( + { + "name": "Test Product 1", + } + ) + + self.board_service1 = self.env["pms.board.service"].create( + { + "name": "Test Board Service 1", + "default_code": "CB1", + } + ) + self.board_service_line1 = self.env["pms.board.service.line"].create( + { + "product_id": self.product1.id, + "pms_board_service_id": self.board_service1.id, + "amount": 10, + "adults": True, + } + ) + + self.board_service_room_type1 = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": self.room_type_double.id, + "pms_board_service_id": self.board_service1.id, + "pms_property_id": self.property.id, + } + ) + self.reservation1 = 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.partner_id.id, + "board_service_room_id": self.board_service_room_type1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + dict_lines = dict() + service_lines = self.reservation1.folio_id.sale_line_ids.filtered("service_id") + for line in service_lines: + dict_lines[line.id] = 1 + self.reservation1.folio_id._create_invoices(lines_to_invoice=dict_lines) + expected_qty_invoiced = sum( + self.reservation1.folio_id.move_ids.invoice_line_ids.mapped("quantity") + ) + self.assertEqual( + expected_qty_invoiced, + sum(self.reservation1.folio_id.sale_line_ids.mapped("qty_invoiced")), + "The quantity of invoiced board services don't match between folio and invoice", + ) + + def test_qty_to_invoice_board_service(self): + """ + Test create and invoice from the Folio, and check qty to invoice + with board service linked + """ + self.product1 = self.env["product.product"].create( + { + "name": "Test Product 1", + } + ) + + self.board_service1 = self.env["pms.board.service"].create( + { + "name": "Test Board Service 1", + "default_code": "CB1", + } + ) + self.board_service_line1 = self.env["pms.board.service.line"].create( + { + "product_id": self.product1.id, + "pms_board_service_id": self.board_service1.id, + "amount": 10, + "adults": True, + } + ) + + self.board_service_room_type1 = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": self.room_type_double.id, + "pms_board_service_id": self.board_service1.id, + "pms_property_id": self.property.id, + } + ) + self.reservation1 = 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.partner_id.id, + "board_service_room_id": self.board_service_room_type1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + dict_lines = dict() + service_lines = self.reservation1.folio_id.sale_line_ids.filtered("service_id") + for line in service_lines: + dict_lines[line.id] = 1 + self.reservation1.folio_id._create_invoices(lines_to_invoice=dict_lines) + expected_qty_to_invoice = sum( + self.reservation1.folio_id.sale_line_ids.filtered("service_id").mapped( + "qty_to_invoice" + ) + ) + self.assertEqual( + expected_qty_to_invoice, + 0, + "The quantity of board services to be invoice is wrong", + ) + + def test_autoinvoice_folio_checkout_property_policy(self): + """ + Test create and invoice the cron by property preconfig automation + -------------------------------------- + Set property default_invoicing_policy to checkout with 0 days with + margin, and check that the folio autoinvoice date is set to last checkout + folio date + """ + # ARRANGE + self.property.default_invoicing_policy = "checkout" + self.property.margin_days_autoinvoice = 0 + + # ACT + self.reservation1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.property.id, + "checkin": datetime.date.today(), + "checkout": datetime.date.today() + datetime.timedelta(days=3), + "adults": 2, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner_id.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + # ASSERT + self.assertIn( + datetime.date.today() + datetime.timedelta(days=3), + self.reservation1.folio_id.mapped("sale_line_ids.autoinvoice_date"), + "The autoinvoice date in folio with property checkout policy is wrong", + ) + + def test_autoinvoice_folio_checkout_partner_policy(self): + """ + Test create and invoice the cron by partner preconfig automation + -------------------------------------- + Set partner invoicing_policy to checkout with 2 days with + margin, and check that the folio autoinvoice date is set to last checkout + folio date + 2 days + """ + # ARRANGE + self.partner_id.invoicing_policy = "checkout" + self.partner_id.margin_days_autoinvoice = 2 + + # ACT + self.reservation1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.property.id, + "checkin": datetime.date.today(), + "checkout": datetime.date.today() + datetime.timedelta(days=3), + "adults": 2, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner_id.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + self.reservation1.reservation_line_ids.default_invoice_to = self.partner_id + + # ASSERT + self.assertEqual( + datetime.date.today() + datetime.timedelta(days=5), + self.reservation1.folio_id.sale_line_ids.filtered( + lambda l: l.invoice_status == "to_invoice" + )[0].autoinvoice_date, + "The autoinvoice date in folio with property checkout policy is wrong", + ) + + def test_autoinvoice_paid_folio_overnights_partner_policy(self): + """ + Test create and invoice the cron by partner preconfig automation + with partner setted as default invoiced to in reservation lines + -------------------------------------- + Set partner invoicing_policy to checkout, create a reservation + with room, board service and normal service, run autoinvoicing + method and check that only room and board service was invoiced + in partner1, the folio must be paid + + """ + # ARRANGE + self.create_configuration_accounting_scenario() + self.partner_id2 = self.env["res.partner"].create( + { + "name": "Sara", + "vat": "54235544A", + "country_id": self.env.ref("base.es").id, + "city": "Madrid", + "zip": "28013", + "street": "Street 321", + } + ) + self.partner_id.invoicing_policy = "checkout" + self.partner_id.margin_days_autoinvoice = 0 + self.product1 = self.env["product.product"].create( + { + "name": "Test Product 1", + } + ) + + self.product2 = self.env["product.product"].create( + { + "name": "Test Product 2", + "lst_price": 100, + } + ) + + self.board_service1 = self.env["pms.board.service"].create( + { + "name": "Test Board Service 1", + "default_code": "CB1", + "amount": 10, + } + ) + + self.board_service_line1 = self.env["pms.board.service.line"].create( + { + "product_id": self.product1.id, + "pms_board_service_id": self.board_service1.id, + "amount": 10, + "adults": True, + } + ) + + self.board_service_room_type1 = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": self.demo_room_type_double.id, + "pms_board_service_id": self.board_service1.id, + "pms_property_id": self.pms_property_demo.id, + } + ) + # ACT + self.reservation1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property_demo.id, + "checkin": datetime.date.today() - datetime.timedelta(days=3), + "checkout": datetime.date.today(), + "adults": 2, + "room_type_id": self.demo_room_type_double.id, + "partner_id": self.partner_id2.id, + "board_service_room_id": self.board_service_room_type1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + self.service = self.env["pms.service"].create( + { + "is_board_service": False, + "product_id": self.product2.id, + "reservation_id": self.reservation1.id, + } + ) + folio = self.reservation1.folio_id + reservation1 = self.reservation1 + reservation1.reservation_line_ids.default_invoice_to = self.partner_id + reservation1.service_ids.filtered( + "is_board_service" + ).default_invoice_to = self.partner_id + + folio.do_payment( + journal=self.env["account.journal"].browse( + reservation1.folio_id.pms_property_id._get_payment_methods().ids[0] + ), + receivable_account=self.env["account.journal"] + .browse(reservation1.folio_id.pms_property_id._get_payment_methods().ids[0]) + .suspense_account_id, + user=self.env.user, + amount=reservation1.folio_id.pending_amount, + folio=folio, + partner=reservation1.partner_id, + date=fields.date.today(), + ) + self.pms_property_demo.autoinvoicing() + + # ASSERT + overnight_sale_lines = self.reservation1.folio_id.sale_line_ids.filtered( + lambda line: line.reservation_line_ids or line.is_board_service + ) + partner_invoice = self.reservation1.folio_id.move_ids.filtered( + lambda inv: inv.partner_id == self.partner_id + ) + self.assertEqual( + partner_invoice.mapped("line_ids.folio_line_ids.id"), + overnight_sale_lines.ids, + "Billed services and overnights invoicing wrong compute", + ) + + def test_not_autoinvoice_unpaid_cancel_folio_partner_policy(self): + """ + Test create and invoice the cron by partner preconfig automation + -------------------------------------- + Set partner invoicing_policy to checkout, create a reservation + with room, board service and normal service, run autoinvoicing + method and check that not invoice was created becouse + the folio is cancel and not paid + """ + # ARRANGE + self.partner_id.invoicing_policy = "checkout" + self.partner_id.margin_days_autoinvoice = 0 + self.product1 = self.env["product.product"].create( + { + "name": "Test Product 1", + } + ) + + self.product2 = self.env["product.product"].create( + { + "name": "Test Product 2", + "lst_price": 100, + } + ) + + self.board_service1 = self.env["pms.board.service"].create( + { + "name": "Test Board Service 1", + "default_code": "CB1", + "amount": 10, + } + ) + + self.board_service_line1 = self.env["pms.board.service.line"].create( + { + "product_id": self.product1.id, + "pms_board_service_id": self.board_service1.id, + "amount": 10, + "adults": True, + } + ) + + self.board_service_room_type1 = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": self.room_type_double.id, + "pms_board_service_id": self.board_service1.id, + "pms_property_id": self.property.id, + } + ) + # ACT + self.reservation1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.property.id, + "checkin": datetime.date.today() - datetime.timedelta(days=3), + "checkout": datetime.date.today(), + "adults": 2, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner_id.id, + "board_service_room_id": self.board_service_room_type1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + self.service = self.env["pms.service"].create( + { + "is_board_service": False, + "product_id": self.product2.id, + "reservation_id": self.reservation1.id, + } + ) + self.reservation1.action_cancel() + self.property.autoinvoicing() + + # ASSERT + partner_invoice = self.reservation1.folio_id.move_ids.filtered( + lambda inv: inv.partner_id == self.partner_id + ) + self.assertEqual( + partner_invoice.mapped("line_ids.folio_line_ids.id"), + [], + "Billed services and overnights invoicing wrong compute", + ) + + def _test_invoice_line_group_by_room_type_sections(self): + """Test create and invoice from the Folio, and check qty invoice/to invoice, + and the grouped invoice lines by room type, by one + line by unit prices/qty with nights""" + + def _test_downpayment(self): + """Test invoice qith a way of downpaument and check dowpayment's + folio line is created and also check a total amount of invoice is + equal to a respective folio's total amount""" + + def _test_invoice_with_discount(self): + """Test create with a discount and check discount applied + on both Folio lines and an inovoice lines""" + + def _test_reinvoice(self): + """Test the compute reinvoice folio take into account + nights and services qty invoiced""" diff --git a/pms/tests/test_pms_folio_prices.py b/pms/tests/test_pms_folio_prices.py new file mode 100644 index 0000000000..da7af4e005 --- /dev/null +++ b/pms/tests/test_pms_folio_prices.py @@ -0,0 +1,11 @@ +from odoo.tests.common import SavepointCase + + +class TestPmsFolioPrice(SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + def test_price_folio(self): + """Test create reservation and services, and check price + tax and discounts""" 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 0000000000..42261b23b1 --- /dev/null +++ b/pms/tests/test_pms_folio_sale_line.py @@ -0,0 +1,1300 @@ +import datetime + +from odoo import fields + +from .common import TestPms + + +class TestPmsFolioSaleLine(TestPms): + @classmethod + def setUpClass(cls): + """ + - common + room_type_avalability_plan + """ + super().setUpClass() + + # create room type + cls.room_type_double = cls.env["pms.room.type"].create( + { + "pms_property_ids": [cls.pms_property1.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": cls.room_type_class1.id, + "price": 25, + } + ) + # create room + cls.room1 = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property1.id, + "name": "Double 101", + "room_type_id": cls.room_type_double.id, + "capacity": 2, + } + ) + cls.room2 = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property1.id, + "name": "Double 102", + "room_type_id": cls.room_type_double.id, + "capacity": 2, + } + ) + + cls.product_test1 = cls.env["product.product"].create( + { + "name": "Test Product 1", + "per_day": True, + } + ) + cls.product_test2 = cls.env["product.product"].create( + { + "name": "Test Product 1", + "per_day": True, + "consumed_on": "after", + } + ) + cls.board_service_test = cls.board_service = cls.env[ + "pms.board.service" + ].create( + { + "name": "Test Board Service", + "default_code": "TPS", + } + ) + cls.env["pms.board.service.line"].create( + { + "pms_board_service_id": cls.board_service_test.id, + "product_id": cls.product_test1.id, + "amount": 8, + "adults": True, + } + ) + cls.board_service_room_type = cls.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": cls.room_type_double.id, + "pms_board_service_id": cls.board_service_test.id, + "pms_property_id": cls.pms_property1.id, + } + ) + cls.extra_service = cls.env["pms.service"].create( + { + "is_board_service": False, + "product_id": cls.product_test2.id, + } + ) + + cls.sale_channel_direct1 = cls.env["pms.sale.channel"].create( + { + "name": "Door", + "channel_type": "direct", + } + ) + + # RESERVATION LINES + def test_comp_fsl_rooms_all_same_group(self): + """ + check the grouping of the reservation lines on the sale line folio + when the price, discount match- + ------------ + reservation with 3 nights with the same price, + should generate just 1 reservation sale line + """ + # ARRANGE + expected_sale_lines = 1 + + # ACT + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "reservation_line_ids": [ + ( + 0, + False, + { + "date": fields.date.today(), + "price": 20, + "discount": 10, + }, + ), + ( + 0, + False, + { + "date": fields.date.today() + datetime.timedelta(days=1), + "price": 20, + "discount": 10, + }, + ), + ( + 0, + False, + { + "date": fields.date.today() + datetime.timedelta(days=2), + "price": 20, + "discount": 10, + }, + ), + ], + "adults": 2, + "room_type_id": self.room_type_double.id, + "partner_id": self.env.ref("base.res_partner_12").id, + "sale_channel_origin_id": self.sale_channel_direct1.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): + """ + Check that a reservation with two nights and different prices per + night generates two sale lines. + ------------ + Create a reservation with a double room as a room type and 2 nights, + which has a price of 25.0 per night. Then the price of one of the reservation + lines is changed to 50.0. As there are two different prices per + night in the reservation the sale lines of the folio should be 2 . + """ + + # ARRANGE + expected_sale_lines = 2 + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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, + "sale_channel_origin_id": self.sale_channel_direct1.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): + """ + Check that a reservation with two nights and different discount per + night generates two sale lines. + ------------ + Create a reservation with a double room as a room type and 2 nights, which has + a default discount of 0 per night. Then the discount of one of the reservation + lines is changed to 50.0. As there are two different discounts per night in the + reservation the sale lines of the folio should be 2. + """ + + # ARRANGE + expected_sale_lines = 2 + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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, + "sale_channel_origin_id": self.sale_channel_direct1.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): + """ + Check that a reservation with two nights and different cancel + discount per night generates two sale lines. + ------------ + Create a reservation with a double room as a room type and 2 nights, + which has a default cancel discount of 0 per night. Then the cancel discount + of one of the reservation lines is changed to 50.0. As there are two + different cancel discount per night in the reservation the sale lines of + the folio should be 2. As one of the reservation lines has a 100% cancel + discount, the sale line should be 1 . + """ + + # ARRANGE + expected_sale_lines = 2 + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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, + "sale_channel_origin_id": self.sale_channel_direct1.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): + """ + Check that a reservation with a 100% cancel discount on one night + does not generate different sale lines. + ---------------- + Create a reservation with a double room as a room type, which has + a default cancel discount of 0 per night. Then the cancel discount + of one of the reservation lines is changed to 100.0. + """ + # ARRANGE + expected_sale_lines = 1 + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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, + "sale_channel_origin_id": self.sale_channel_direct1.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): + """ + Check when adding a night to a reservation after creating it and this night + has the same price, cancel and cancel discount values, the sales line that + were created with the reservation are maintained. + --------- + Create a reservation of 2 nights for a double room. The value of the sale lines + of that reservation is stored in a variable. Then one more night is added to the + reservation and it is verified that the sale lines are the same as the value of + the previously saved variable. + """ + # ARRANGE + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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, + "sale_channel_origin_id": self.sale_channel_direct1.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): + """ + Check when a night is removed from a reservation after creating + it, the sales lines that were created with the reservation are kept. + --------- + Create a reservation of 2 nights for a double room. The value of the sale lines + of that reservation is stored in a variable. Then it is removed one night at + reservation and it is verified that the reservation sale lines are equal to the value of + the previously saved variable. + """ + # ARRANGE + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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, + "sale_channel_origin_id": self.sale_channel_direct1.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): + """ + Check that when changing the price of all the reservation lines in a + reservation, which before the change had the same price, discount + and cancel discount values, the same sale lines that existed before + the change are kept. + ------------------ + Create a reservation of 2 nights for a double room with a price of 25.0. + The value of the sale lines of that reservation is stored in a variable. + Then the value of the price of all the reservation lines is changed to 50.0 + and it is verified that the reservation sale lines are equal to the value + of the previously saved variable. + """ + # ARRANGE + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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, + "sale_channel_origin_id": self.sale_channel_direct1.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): + + """ + Check that the board services of reservation with the same price, discount + and cancel discount values, should only generate one sale line. + ---------------- + Create a reservation of 2 nights, for a double room with a board service + room per night. Then it is verified that the length of the sale lines of the + board services in the reservation is equal to 1. + """ + # ARRANGE + expected_board_service_sale_lines = 1 + + # ACT + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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": self.board_service_room_type.id, + "sale_channel_origin_id": self.sale_channel_direct1.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): + """ + Check that the board services of reservation with different prices + should generate several sale lines. + ---------------- + Create a reservation of 2 nights, for a double room with a board service + room per night. Then change the price of the first board service line to + 1.0 and it is verified that the length of the sale lines of the board services + in the reservation is equal to 2 because there are 2 different board service + prices in the reservation. + """ + # ARRANGE + expected_board_service_sale_lines = 2 + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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": self.board_service_room_type.id, + "sale_channel_origin_id": self.sale_channel_direct1.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): + """ + Check that the board services of reservation with different discounts + should generate several sale lines. + ---------------- + Create a reservation of 2 nights, for a double room with a board service + room per night. Then change the discount of the first board service line + to 1.0 and it is verified that the length of the sale lines of the board services + in the reservation is equal to 2 because there are 2 different board service + discounts in the reservation. + """ + # ARRANGE + expected_board_service_sale_lines = 2 + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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": self.board_service_room_type.id, + "sale_channel_origin_id": self.sale_channel_direct1.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): + """ + Check that the board services of reservation with different cancel + discounts should generate several sale lines. + ---------------- + Create a reservation of 2 nights, for a double room with a board service + room per night. Then change the cancel discount of the first board service line + to 1.0 and it is verified that the length of the sale lines of the board services + in the reservation is equal to 2 because there are 2 different board service + cancel discounts in the reservation. + """ + + # ARRANGE + expected_board_service_sale_lines = 2 + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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": self.board_service_room_type.id, + "sale_channel_origin_id": self.sale_channel_direct1.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): + """ + Check that the board services of reservation with 100% cancel + discount should generate only 1 sale line. + ---------------- + Create a reservation of 2 nights, for a double room with a board service + room per night. Then change the cancel discount of the first board service line + to 100.0 and it is verified that the length of the sale lines of the board services + in the reservation is equal to 1. + """ + + # ARRANGE + expected_board_service_sale_lines = 1 + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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": self.board_service_room_type.id, + "sale_channel_origin_id": self.sale_channel_direct1.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): + """ + Check when adding a night to a reservation with board services room, + after creating it and this board service has the same price, cancel + and cancel discount values, the sale lines that were created with the + reservation are kept. + --------- + Create a reservation of 2 nights for a double room with a board service. + The value of the sale lines of that board services is stored in a variable. + Then one more night is added to the reservation and it is verified that + the sale lines are the same as the value of the previously saved variable. + """ + + # ARRANGE + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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": self.board_service_room_type.id, + "sale_channel_origin_id": self.sale_channel_direct1.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): + """ + Check when removing a night to a reservation with board services room, + after creating it and this board service has the same price, cancel + and cancel discount values, the sale lines that were created with the + reservation are kept. + --------- + Create a reservation of 2 nights for a double room with a board service. + The value of the sale lines of that board services is stored in a variable. + Then one night is removed to the reservation and it is verified that + the sale lines are the same as the value of the previously saved variable. + """ + + # ARRANGE + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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": self.board_service_room_type.id, + "sale_channel_origin_id": self.sale_channel_direct1.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): + """ + Check that when changing the price of all board services in a + reservation, which before the change had the same price, discount + and cancel discount values, the same sale lines that existed before + the change are kept. + ------------------ + Create a reservation of 2 nights for a double room with a board service + price of 8.0. The value of the sale lines of the board services is stored + in a variable. Then the value of the price of all the reservation board services + is changed to 50 and it is verified that the reservation sale lines are equal to + the value of the previously saved variable. + """ + # ARRANGE + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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": self.board_service_room_type.id, + "sale_channel_origin_id": self.sale_channel_direct1.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): + """ + Check that when adding a service that is not a board service to a + reservation with the same price, cancel and cancel discount, the + number of sales lines is kept. + ------------------ + Create a 2 night reservation. Then a service is added with + is_board_service = False and it is verified that the length of + the sale lines of the reservation is 1. + """ + # ARRANGE + expected_extra_service_sale_lines = 1 + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + # ACT + r_test.service_ids = [(4, self.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 == self.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): + """ + Check that a reservation of several nights and with different + prices per day on services should generate several sale lines. + ----------------- + Create a reservation for 2 nights. Then add a service to this + reservation and the price of the first service line is changed + to 44.5. It is verified that the length of the reservation's sale + lines is equal to 2, because there are two different prices per day + for service lines. + """ + + # ARRANGE + expected_extra_service_sale_lines = 2 + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r_test.service_ids = [(4, self.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 == self.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): + """ + Check that a reservation of several nights and with different + discount per day on services should generate several sale lines. + ----------------- + Create a reservation for 2 nights. Then add a service to this + reservation and the discount of the first service line is changed + to 44.5. It is verified that the length of the reservation's sale + lines is equal to 2, because there are two different discounts per day + for service lines. + """ + + # ARRANGE + expected_extra_service_sale_lines = 2 + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r_test.service_ids = [(4, self.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 == self.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): + """ + Check that a reservation of several nights and with different + cancel discount per day on services should generate several sale + lines. + ----------------- + Create a reservation for 2 nights. Then add a service to this + reservation and the cancel discount of the first service line is changed + to 44.5. It is verified that the length of the reservation's sale + lines is equal to 2, because there are two different cancel discounts per + day for service lines. + """ + + # ARRANGE + expected_extra_service_sale_lines = 2 + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r_test.service_ids = [(4, self.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 == self.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): + """ + Check that a reservation of several nights and with a 100% cancel + discount for a service should generate only 1 sale line. + ----------------- + Create a reservation for 2 nights. Then add a service to this + reservation and the cancel discount of the first service line is changed + to 100%. It is verified that the length of the reservation sale + lines is equal to 1. + """ + + # ARRANGE + expected_extra_service_sale_lines = 1 + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r_test.service_ids = [(4, self.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 == self.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): + """ + Check when adding a night to a reservation after creating it and this services + has the same price, cancel and cancel discount values, the sales line that + were created with the reservation are maintained. + --------- + Create a reservation of 2 nights for a double room and add a service to this + reservation. The value of the sale lines of that reservation services is stored + in a variable. Then one more night is added to the reservation and it is verified + that the reservation service sale lines are the same as the value of the previously + saved variable. + """ + + # ARRANGE + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r_test.service_ids = [(4, self.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 == self.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 == self.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): + """ + Check when removing a night to a reservation after creating it and this services + has the same price, cancel and cancel discount values, the sales line that + were created with the reservation are maintained. + --------- + Create a reservation of 2 nights for a double room and add a service to this + reservation. The value of the sale lines of the services is stored + in a variable. Then one night is removed to the reservation and it is verified + that the reservation service sale lines are the same as the value of the previously + saved variable. + """ + # ARRANGE + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r_test.service_ids = [(4, self.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 == self.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 == self.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. + """ + Check that when changing the price of all services in a + reservation, which before the change had the same price, discount + and cancel discount values, the same sale lines that existed before + the change are kept. + ------------------ + Create a reservation of 2 nights for a double room and add a service to this + reservation. The value of the sale lines of the services is stored + in a variable. Then the value of the price of all the reservation services + is changed to 50 and it is verified that the reservation service sale lines + are equal to the value of the previously saved variable. + """ + + # ARRANGE + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r_test.service_ids = [(4, self.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 == self.extra_service + )[0] + + # ACT + r_test.service_ids.filtered( + lambda x: x.id == self.extra_service.id + ).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 == self.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 + """ + Check that when adding a service that is not a board service to a + folio with the same price, cancel and cancel discount, the number + of sales lines is kept. + ------------------ + Create a 2 night reservation. Then a service is added with + is_board_service = False and it is verified that the length of + the sale lines of the folio is 1. + """ + # ARRANGE + expected_folio_service_sale_lines = 1 + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + # ACT + r_test.folio_id.service_ids = [(4, self.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 == self.extra_service + ) + ), + "Folio should contain {} folio service sale lines".format( + expected_folio_service_sale_lines + ), + ) + + def test_comp_fsl_fol_extra_services_two(self): + """ + Check that when adding several services to a folio, + several sale lines should be generated on the folio. + ----------------- + Create a 2 night reservation. Two services are added + to the reservation and it is verified that the length + of the folio sale lines is equal to 2. + """ + + # ARRANGE + expected_folio_service_sale_lines = 2 + product_test2 = self.env["product.product"].create( + { + "name": "Test Product 1", + } + ) + + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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, + "sale_channel_origin_id": self.sale_channel_direct1.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, self.extra_service.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 + ), + ) + + def test_no_sale_lines_staff_reservation(self): + """ + Check that the sale_line_ids of a folio whose reservation + is of type 'staff' are created with price 0. + ----- + A reservation is created with the reservation_type field + with value 'staff'. Then it is verified that the + sale_line_ids of the folio created with the creation of + the reservation have price 0. + """ + # ARRANGE + self.partner1 = self.env["res.partner"].create({"name": "Alberto"}) + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=1) + # ACT + reservation = self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "reservation_type": "staff", + "sale_channel_origin_id": self.sale_channel_direct1.id, + "adults": 1, + } + ) + # ASSERT + self.assertEqual( + reservation.folio_id.sale_line_ids.mapped("price_unit")[0], + 0, + "Staff folio sale lines should have price 0", + ) + + def test_no_sale_lines_out_reservation(self): + """ + Check that the sale_line_ids of a folio whose reservation + is of type 'out' are not created. + ----- + A reservation is created with the reservation_type field + with value 'out'. Then it is verified that the + sale_line_ids of the folio created with the creation of + the reservation are equal to False. + """ + # ARRANGE + self.partner1 = self.env["res.partner"].create({"name": "Alberto"}) + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=3) + closure_reason = self.env["room.closure.reason"].create( + { + "name": "test closure reason", + "description": "test clopsure reason description", + } + ) + # ACT + reservation = self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "reservation_type": "out", + "closure_reason_id": closure_reason.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + # ASSERT + self.assertFalse( + reservation.folio_id.sale_line_ids, + "Folio sale lines should not be generated for a out of service type reservation ", + ) diff --git a/pms/tests/test_pms_invoice_refund.py b/pms/tests/test_pms_invoice_refund.py new file mode 100644 index 0000000000..d532b92194 --- /dev/null +++ b/pms/tests/test_pms_invoice_refund.py @@ -0,0 +1,11 @@ +from freezegun import freeze_time + +from odoo.tests.common import SavepointCase + +freeze_time("2000-02-02") + + +class TestPmsInvoiceRefund(SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() diff --git a/pms/tests/test_pms_multiproperty.py b/pms/tests/test_pms_multiproperty.py new file mode 100644 index 0000000000..de81767fd4 --- /dev/null +++ b/pms/tests/test_pms_multiproperty.py @@ -0,0 +1,1065 @@ +import datetime + +from odoo import fields +from odoo.exceptions import UserError + +from .common import TestPms + + +class TestPmsMultiproperty(TestPms): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.pms_property2 = cls.env["pms.property"].create( + { + "name": "Pms_property_test2", + "company_id": cls.company1.id, + "default_pricelist_id": cls.pricelist1.id, + } + ) + + cls.pms_property3 = cls.env["pms.property"].create( + { + "name": "Pms_property_test3", + "company_id": cls.company1.id, + "default_pricelist_id": cls.pricelist1.id, + } + ) + + def test_availability_closed_no_room_type_check_property(self): + """ + Check that availability rules are applied to the correct properties. + ---------- + Check that for that date test_property1 doesnt have rooms available + (of that type:room_type1), + instead, property2 has room_type1 available + """ + # ARRANGE + self.pricelist2 = self.env["product.pricelist"].create( + { + "name": "test pricelist 1", + "pms_property_ids": [ + (4, self.pms_property1.id), + (4, self.pms_property2.id), + ], + "availability_plan_id": self.availability_plan1.id, + "is_pms_available": True, + } + ) + self.availability_plan1 = self.env["pms.availability.plan"].create( + { + "name": "Availability plan for TEST", + "pms_pricelist_ids": [(6, 0, [self.pricelist2.id])], + "pms_property_ids": [ + (4, self.pms_property1.id), + (4, self.pms_property2.id), + ], + } + ) + self.room_type1 = self.env["pms.room.type"].create( + { + "pms_property_ids": [ + (4, self.pms_property1.id), + (4, self.pms_property2.id), + ], + "name": "Special Room Test", + "default_code": "SP_Test", + "class_id": self.room_type_class1.id, + } + ) + self.room1 = self.env["pms.room"].create( + { + "pms_property_id": self.pms_property1.id, + "name": "Double 201 test", + "room_type_id": self.room_type1.id, + "capacity": 2, + } + ) + # pms.room + self.room2 = self.env["pms.room"].create( + { + "pms_property_id": self.pms_property2.id, + "name": "Double 202 test", + "room_type_id": self.room_type1.id, + "capacity": 2, + } + ) + self.room_type_availability_rule1 = self.env[ + "pms.availability.plan.rule" + ].create( + { + "availability_plan_id": self.availability_plan1.id, + "room_type_id": self.room_type1.id, + "date": (fields.datetime.today() + datetime.timedelta(days=2)).date(), + "closed": True, + "pms_property_id": self.pms_property1.id, + } + ) + self.room_type_availability_rule2 = self.env[ + "pms.availability.plan.rule" + ].create( + { + "availability_plan_id": self.availability_plan1.id, + "room_type_id": self.room_type1.id, + "date": (fields.datetime.today() + datetime.timedelta(days=2)).date(), + "pms_property_id": self.pms_property2.id, + } + ) + + properties = [ + {"property": self.pms_property1.id, "value": False}, + {"property": self.pms_property2.id, "value": True}, + ] + + for p in properties: + with self.subTest(k=p): + # ACT + pms_property = self.env["pms.property"].browse(p["property"]) + pms_property = pms_property.with_context( + checkin=fields.date.today(), + checkout=( + fields.datetime.today() + datetime.timedelta(days=2) + ).date(), + room_type_id=self.room_type1.id, + pricelist_id=self.pricelist2.id, + ) + rooms_avail = pms_property.free_room_ids + + # ASSERT + self.assertEqual( + len(rooms_avail) > 0, p["value"], "Availability is not correct" + ) + + # AMENITY + def test_amenity_property_not_allowed(self): + """ + Creation of a Amenity with Properties incompatible with it Amenity Type + + +-----------------------------------+-----------------------------------+ + | Amenity Type (TestAmenityType1) | Amenity (TestAmenity1) | + +-----------------------------------+-----------------------------------+ + | Property1 - Property2 | Property1 - Property2 - Property3 | + +-----------------------------------+-----------------------------------+ + """ + # ARRANGE + AmenityType = self.env["pms.amenity.type"] + Amenity = self.env["pms.amenity"] + amenity_type1 = AmenityType.create( + { + "name": "TestAmenityType1", + "pms_property_ids": [ + (4, self.pms_property1.id), + (4, self.pms_property2.id), + ], + } + ) + # ACT & ASSERT + with self.assertRaises(UserError), self.cr.savepoint(): + Amenity.create( + { + "name": "TestAmenity1", + "pms_amenity_type_id": amenity_type1.id, + "pms_property_ids": [ + ( + 6, + 0, + [ + self.pms_property1.id, + self.pms_property2.id, + self.pms_property3.id, + ], + ) + ], + } + ) + + # AVAILABILITY PLAN RULES + def test_check_property_availability_room_type(self): + """ + Check integrity between availability properties and room_type properties. + Test cases when creating a availability_rule: + Allowed properties: + Room Type(room_type1) --> pms_property1, pms_property_4 + Availability Plan(availability_example) --> pms_property1, pms_property2 + + Both cases throw an exception: + # 1:Rule for property2, + # it is allowed in availability_plan but not in room_type + # 2:Rule for property4, + # it is allowed in room_type, but not in availability_plan + """ + # ARRANGE + self.pms_property4 = self.env["pms.property"].create( + { + "name": "Property 3", + "company_id": self.company1.id, + "default_pricelist_id": self.pricelist1.id, + } + ) + self.pricelist2 = self.env["product.pricelist"].create( + { + "name": "test pricelist 1", + "pms_property_ids": [ + (4, self.pms_property1.id), + (4, self.pms_property2.id), + ], + "availability_plan_id": self.availability_plan1.id, + "is_pms_available": True, + } + ) + # create new room_type + self.room_type1 = self.env["pms.room.type"].create( + { + "pms_property_ids": [ + (4, self.pms_property1.id), + (4, self.pms_property4.id), + ], + "name": "Special Room Test", + "default_code": "SP_Test", + "class_id": self.room_type_class1.id, + } + ) + # ACT + self.availability_plan1 = self.env["pms.availability.plan"].create( + { + "name": "Availability plan for TEST", + "pms_pricelist_ids": [(6, 0, [self.pricelist2.id])], + "pms_property_ids": [ + (4, self.pms_property1.id), + (4, self.pms_property2.id), + ], + } + ) + self.availability_rule1 = self.env["pms.availability.plan.rule"].create( + { + "availability_plan_id": self.availability_plan1.id, + "room_type_id": self.room_type1.id, + "date": (fields.datetime.today() + datetime.timedelta(days=2)).date(), + "closed": True, + "pms_property_id": self.pms_property1.id, + } + ) + + test_cases = [ + { + "pms_property_id": self.pms_property2.id, + }, + { + "pms_property_id": self.pms_property4.id, + }, + ] + # ASSERT + for test_case in test_cases: + with self.subTest(k=test_case): + with self.assertRaises(UserError): + self.availability_rule1.pms_property_id = test_case[ + "pms_property_id" + ] + + # BOARD SERVICE LINE + def test_pms_bsl_product_property_integrity(self): + """ + Creation of a board service line without property, of a product + only available for a specific property. + """ + # ARRANGE + product1 = self.env["product.product"].create( + {"name": "Product", "pms_property_ids": [self.pms_property1.id]} + ) + board_service1 = self.env["pms.board.service"].create( + { + "name": "Board Service", + "default_code": "CB", + } + ) + # ACT & ASSERT + with self.assertRaises( + UserError, msg="Board service line shouldnt be created." + ): + self.env["pms.board.service.line"].create( + { + "product_id": product1.id, + "pms_board_service_id": board_service1.id, + "adults": True, + } + ) + + def test_pms_bsl_board_service_property_integrity(self): + """ + Creation of a board service line without property, of board service + only available for a specific property. + """ + # ARRANGE + pms_property2 = self.env["pms.property"].create( + { + "name": "Property 1", + "company_id": self.company1.id, + "default_pricelist_id": self.pricelist1.id, + } + ) + product1 = self.env["product.product"].create( + {"name": "Product", "pms_property_ids": [self.pms_property1.id]} + ) + + board_service1 = self.env["pms.board.service"].create( + { + "name": "Board Service", + "default_code": "CB", + "pms_property_ids": [pms_property2.id], + } + ) + # ACT & ASSERT + with self.assertRaises( + UserError, msg="Board service line shouldnt be created." + ): + self.env["pms.board.service.line"].create( + { + "product_id": product1.id, + "pms_board_service_id": board_service1.id, + "adults": True, + } + ) + + def test_pms_bsl_board_service_line_prop_integrity(self): + """ + Creation of a board service line with a specific property, + of board service without property. + """ + # ARRANGE + pms_property2 = self.env["pms.property"].create( + { + "name": "Property 1", + "company_id": self.company1.id, + "default_pricelist_id": self.pricelist1.id, + } + ) + product1 = self.env["product.product"].create( + {"name": "Product", "pms_property_ids": [self.pms_property1.id]} + ) + board_service1 = self.env["pms.board.service"].create( + { + "name": "Board Service", + "default_code": "CB", + } + ) + # ACT & ASSERT + with self.assertRaises( + UserError, msg="Board service line shouldnt be created." + ): + self.env["pms.board.service.line"].create( + { + "product_id": product1.id, + "pms_board_service_id": board_service1.id, + "pms_property_ids": [pms_property2.id], + "adults": True, + } + ) + + # BOARD SERVICE ROOM TYPE + def test_create_rt_props_gt_bs_props(self): + """ + Create board service for a room type and the room type + have MORE properties than the board service. + Record of board_service_room_type should contain the + board service properties. + """ + # ARRANGE + pms_property2 = self.env["pms.property"].create( + { + "name": "Property 2", + "company_id": self.company1.id, + "default_pricelist_id": self.pricelist1.id, + } + ) + + room_type_double = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id, pms_property2.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": self.room_type_class1.id, + "price": 25, + } + ) + board_service_test = self.board_service = self.env["pms.board.service"].create( + { + "name": "Test Board Service", + "default_code": "TPS", + "pms_property_ids": [self.pms_property1.id], + } + ) + # ACT + new_bsrt = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": room_type_double.id, + "pms_board_service_id": board_service_test.id, + "pms_property_id": self.pms_property1.id, + } + ) + # ASSERT + self.assertIn( + new_bsrt.pms_property_id.id, + board_service_test.pms_property_ids.ids, + "Record of board_service_room_type should contain the" + " board service properties.", + ) + + def test_create_rt_props_lt_bs_props(self): + """ + Create board service for a room type and the room type + have LESS properties than the board service. + Record of board_service_room_type should contain the + room types properties. + """ + # ARRANGE + pms_property2 = self.env["pms.property"].create( + { + "name": "Property 2", + "company_id": self.company1.id, + "default_pricelist_id": self.pricelist1.id, + } + ) + room_type1 = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": self.room_type_class1.id, + "price": 25, + } + ) + board_service1 = self.board_service = self.env["pms.board.service"].create( + { + "name": "Test Board Service", + "default_code": "TPS", + "pms_property_ids": [self.pms_property1.id, pms_property2.id], + } + ) + # ACT + new_bsrt = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": room_type1.id, + "pms_board_service_id": board_service1.id, + "pms_property_id": self.pms_property1.id, + } + ) + # ASSERT + self.assertIn( + new_bsrt.pms_property_id.id, + room_type1.pms_property_ids.ids, + "Record of board_service_room_type should contain the" + " room types properties.", + ) + + def _test_create_rt_props_eq_bs_props(self): + """ + Create board service for a room type and the room type + have THE SAME properties than the board service. + Record of board_service_room_type should contain the + room types properties that matchs with the board + service properties + """ + # ARRANGE + room_type1 = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": self.room_type_class1.id, + "price": 25, + } + ) + board_service1 = self.board_service = self.env["pms.board.service"].create( + { + "name": "Test Board Service", + "default_code": "TPS", + "pms_property_ids": [self.pms_property1.id], + } + ) + # ACT + new_bsrt = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": room_type1.id, + "pms_board_service_id": board_service1.id, + "pms_property_id": self.pms_property1.id, + } + ) + # ASSERT + self.assertIn( + new_bsrt.pms_property_ids.ids == room_type1.pms_property_ids.ids + and new_bsrt.pms_property_ids.ids == board_service1.pms_property_ids.ids, + "Record of board_service_room_type should contain the room " + "types properties and matchs with the board service properties", + ) + + def test_create_rt_no_props_and_bs_props(self): + """ + Create board service for a room type and the room type + hasn't properties but the board services. + Record of board_service_room_type should contain the + board service properties. + """ + # ARRANGE + room_type1 = self.env["pms.room.type"].create( + { + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": self.room_type_class1.id, + "price": 25, + } + ) + board_service1 = self.board_service = self.env["pms.board.service"].create( + { + "name": "Test Board Service", + "default_code": "TPS", + "pms_property_ids": [self.pms_property1.id], + } + ) + # ACT + new_bsrt = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": room_type1.id, + "pms_board_service_id": board_service1.id, + "pms_property_id": self.pms_property1.id, + } + ) + # ASSERT + self.assertIn( + new_bsrt.pms_property_id.id, + board_service1.pms_property_ids.ids, + "Record of board_service_room_type should contain the" + " board service properties.", + ) + + def test_create_rt_props_and_bs_no_props(self): + """ + Create board service for a room type and the board service + hasn't properties but the room type. + Record of board_service_room_type should contain the + room type properties. + """ + # ARRANGE + pms_property2 = self.env["pms.property"].create( + { + "name": "Property 2", + "company_id": self.company1.id, + "default_pricelist_id": self.pricelist1.id, + } + ) + room_type1 = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id, pms_property2.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": self.room_type_class1.id, + "price": 25, + } + ) + board_service1 = self.board_service = self.env["pms.board.service"].create( + { + "name": "Test Board Service", + "default_code": "TPS", + "pms_property_ids": [self.pms_property1.id], + } + ) + # ACT + new_bsrt = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": room_type1.id, + "pms_board_service_id": board_service1.id, + "pms_property_id": self.pms_property1.id, + } + ) + # ASSERT + self.assertIn( + new_bsrt.pms_property_id.id, + room_type1.pms_property_ids.ids, + "Record of board_service_room_type should contain the" + " room type properties.", + ) + + def test_create_rt_no_props_and_bs_no_props(self): + """ + Create board service for a room type and the board service + has no properties and neither does the room type + Record of board_service_room_type shouldnt contain properties. + """ + # ARRANGE + + room_type1 = self.env["pms.room.type"].create( + { + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": self.room_type_class1.id, + "price": 25, + } + ) + board_service1 = self.board_service = self.env["pms.board.service"].create( + { + "name": "Test Board Service", + "default_code": "TPS", + } + ) + # ACT + new_bsrt = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": room_type1.id, + "pms_board_service_id": board_service1.id, + } + ) + # ASSERT + self.assertFalse( + new_bsrt.pms_property_id.id, + "Record of board_service_room_type shouldnt contain properties.", + ) + + def test_pms_bsrtl_product_property_integrity(self): + """ + Creation of a board service room type line without property, of a product + only available for a specific property. + """ + # ARRANGE + + product1 = self.env["product.product"].create( + {"name": "Product", "pms_property_ids": self.pms_property1} + ) + board_service1 = self.env["pms.board.service"].create( + { + "name": "Board Service", + "default_code": "CB", + } + ) + room_type1 = self.env["pms.room.type"].create( + { + "name": "Room Type", + "default_code": "Type1", + "class_id": self.room_type_class1.id, + } + ) + board_service_room_type1 = self.env["pms.board.service.room.type"].create( + { + "pms_board_service_id": board_service1.id, + "pms_room_type_id": room_type1.id, + } + ) + + # ACT & ASSERT + with self.assertRaises( + UserError, msg="Board service room type line shouldnt be created." + ): + self.env["pms.board.service.room.type.line"].create( + { + "pms_board_service_room_type_id": board_service_room_type1.id, + "product_id": product1.id, + } + ) + + def test_pms_bsrtl_board_service_line_prop_integrity(self): + """ + Creation of a board service room type line with a specific property, + of board service without property. + """ + # ARRANGE + product1 = self.env["product.product"].create( + {"name": "Product", "pms_property_ids": [self.pms_property1.id]} + ) + board_service1 = self.env["pms.board.service"].create( + { + "name": "Board Service", + "default_code": "CB", + } + ) + + room_type1 = self.env["pms.room.type"].create( + { + "name": "Room Type", + "default_code": "Type1", + "class_id": self.room_type_class1.id, + } + ) + board_service_room_type1 = self.env["pms.board.service.room.type"].create( + { + "pms_board_service_id": board_service1.id, + "pms_room_type_id": room_type1.id, + } + ) + + # ACT & ASSERT + with self.assertRaises( + UserError, msg="Board service line shouldnt be created." + ): + self.env["pms.board.service.room.type.line"].create( + { + "product_id": product1.id, + "pms_property_id": self.pms_property2.id, + "pms_board_service_room_type_id": board_service_room_type1.id, + } + ) + + # PMS.FOLIO + def test_folio_closure_reason_consistency_properties(self): + """ + Check the multiproperty consistency between + clousure reasons and folios + ------- + create multiproperty scenario (3 properties in total) and + a new clousure reason in pms_property1 and pms_property2, then, create + a new folio in property3 and try to set the clousure_reason + waiting a error property consistency. + """ + # ARRANGE + cl_reason = self.env["room.closure.reason"].create( + { + "name": "closure_reason_test", + "pms_property_ids": [ + (4, self.pms_property1.id), + (4, self.pms_property2.id), + ], + } + ) + + # ACTION & ASSERT + with self.assertRaises( + UserError, + msg="Folio created with clousure_reason_id with properties inconsistence", + ): + self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property3.id, + "closure_reason_id": cl_reason.id, + } + ) + + # PRICELIST + def test_inconsistency_property_pricelist_item(self): + """ + Check a pricelist item and its pricelist are inconsistent with the property. + Create a pricelist item that belongs to a property and check if + a pricelist that belongs to a diferent one, cannot be created. + """ + # ARRANGE + # ACT & ASSERT + self.pricelist2 = self.env["product.pricelist"].create( + { + "name": "test pricelist 1", + "pms_property_ids": [ + (4, self.pms_property1.id), + (4, self.pms_property2.id), + ], + "availability_plan_id": self.availability_plan1.id, + "is_pms_available": True, + } + ) + self.room_type1 = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id, self.pms_property2.id], + "name": "Single", + "default_code": "SIN", + "class_id": self.room_type_class1.id, + "list_price": 30, + } + ) + with self.assertRaises(UserError): + self.item1 = self.env["product.pricelist.item"].create( + { + "name": "item_1", + "applied_on": "0_product_variant", + "product_id": self.room_type1.product_id.id, + "date_start": datetime.datetime.today(), + "date_end": datetime.datetime.today() + datetime.timedelta(days=1), + "fixed_price": 40.0, + "pricelist_id": self.pricelist2.id, + "pms_property_ids": [self.pms_property3.id], + } + ) + + def test_inconsistency_cancelation_rule_property(self): + """ + Check a cancelation rule and its pricelist are inconsistent with the property. + Create a cancelation rule that belongs to a two properties and check if + a pricelist that belongs to a diferent properties, cannot be created. + """ + # ARRANGE + + Pricelist = self.env["product.pricelist"] + # ACT + self.cancelation_rule1 = self.env["pms.cancelation.rule"].create( + { + "name": "Cancelation Rule Test", + "pms_property_ids": [self.pms_property1.id, self.pms_property3.id], + } + ) + # ASSERT + with self.assertRaises(UserError): + Pricelist.create( + { + "name": "Pricelist Test", + "pms_property_ids": [self.pms_property1.id, self.pms_property2.id], + "cancelation_rule_id": self.cancelation_rule1.id, + "is_pms_available": True, + } + ) + + def test_inconsistency_availability_plan_property(self): + """ + Check a availability plan and its pricelist are inconsistent with the property. + Create a availability plan that belongs to a two properties and check if + a pricelist that belongs to a diferent properties, cannot be created. + """ + self.availability_plan1 = self.env["pms.availability.plan"].create( + {"name": "Availability Plan", "pms_property_ids": [self.pms_property1.id]} + ) + with self.assertRaises(UserError): + self.env["product.pricelist"].create( + { + "name": "Pricelist", + "pms_property_ids": [self.pms_property2.id], + "availability_plan_id": self.availability_plan1.id, + "is_pms_available": True, + } + ) + + def _test_multiproperty_checks(self): + """ + # TEST CASE + Multiproperty checks in reservation + +---------------+------+------+------+----+----+ + | reservation | property1 | + +---------------+------+------+------+----+----+ + | room | property2 | + | room_type | property2, property3 | + | board_service | property2, property3 | + | pricelist | property2, property3 | + +---------------+------+------+------+----+----+ + """ + # ARRANGE + self.board_service1 = self.env["pms.board.service"].create( + { + "name": "Board Service Test", + "default_code": "CB", + } + ) + host1 = self.env["res.partner"].create( + { + "name": "Miguel", + "mobile": "654667733", + "email": "miguel@example.com", + } + ) + self.sale_channel_direct1 = self.env["pms.sale.channel"].create( + { + "name": "Door", + "channel_type": "direct", + } + ) + self.reservation1 = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "pms_property_id": self.pms_property1.id, + "partner_id": host1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + room_type_test = self.env["pms.room.type"].create( + { + "pms_property_ids": [ + (4, self.pms_property3.id), + (4, self.pms_property2.id), + ], + "name": "Single", + "default_code": "SIN", + "class_id": self.room_type_class1.id, + "list_price": 30, + } + ) + + room = self.env["pms.room"].create( + { + "name": "Room 101", + "pms_property_id": self.pms_property2.id, + "room_type_id": room_type_test.id, + } + ) + + pricelist2 = self.env["product.pricelist"].create( + { + "name": "pricelist_test", + "pms_property_ids": [ + (4, self.pms_property2.id), + (4, self.pms_property3.id), + ], + "availability_plan_id": self.availability_plan1.id, + "is_pms_available": True, + } + ) + + board_service_room_type1 = self.env["pms.board.service.room.type"].create( + { + "pms_board_service_id": self.board_service1.id, + "pms_room_type_id": room_type_test.id, + "pms_property_ids": [self.pms_property2.id, self.pms_property3.id], + } + ) + test_cases = [ + {"preferred_room_id": room.id}, + {"room_type_id": room_type_test.id}, + {"pricelist_id": pricelist2.id}, + {"board_service_room_id": board_service_room_type1.id}, + ] + + for test_case in test_cases: + with self.subTest(k=test_case): + with self.assertRaises(UserError): + self.reservation1.write(test_case) + + # ROOM + def test_inconsistency_room_ubication_property(self): + """ + Room property and its ubication properties are inconsistent. + A Room with property that is not included in available properties + for its ubication cannot be created. + """ + # ARRANGE + self.room_type1 = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id, self.pms_property2.id], + "name": "Single", + "default_code": "SI", + "class_id": self.room_type_class1.id, + "list_price": 30, + } + ) + ubication1 = self.env["pms.ubication"].create( + { + "name": "UbicationTest", + "pms_property_ids": [ + (4, self.pms_property1.id), + ], + } + ) + # ACT & ASSERT + with self.assertRaises( + UserError, + msg="The room should not be created if its property is not included " + "in the available properties for its ubication.", + ): + self.env["pms.room"].create( + { + "name": "Room 101", + "pms_property_id": self.pms_property2.id, + "room_type_id": self.room_type1.id, + "ubication_id": ubication1.id, + } + ) + + def test_consistency_room_ubication_property(self): + """ + Room property and its ubication properties are consistent. + A Room with property included in available properties + for its ubication can be created. + """ + # ARRANGE + self.room_type1 = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id, self.pms_property2.id], + "name": "Single", + "default_code": "SI", + "class_id": self.room_type_class1.id, + "list_price": 30, + } + ) + ubication1 = self.env["pms.ubication"].create( + { + "name": "UbicationTest", + "pms_property_ids": [ + (4, self.pms_property1.id), + ], + } + ) + # ACT + new_room1 = self.env["pms.room"].create( + { + "name": "Room 101", + "pms_property_id": self.pms_property1.id, + "room_type_id": self.room_type1.id, + "ubication_id": ubication1.id, + } + ) + # ASSERT + self.assertIn( + new_room1.pms_property_id, + ubication1.pms_property_ids, + "The room should be created if its property belongs to the availabe" + "properties for its ubication.", + ) + + def test_inconsistency_room_type_property(self): + """ + Room property and its room type properties are inconsistent. + A Room with property that is not included in available properties + for its room type cannot be created. + """ + # ARRANGE + self.pms_property3 = self.env["pms.property"].create( + { + "name": "Property_3", + "company_id": self.company1.id, + "default_pricelist_id": self.pricelist1.id, + } + ) + self.room_type1 = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id, self.pms_property2.id], + "name": "Single", + "default_code": "SI", + "class_id": self.room_type_class1.id, + "list_price": 30, + } + ) + # ACT & ARRANGE + with self.assertRaises( + UserError, + msg="The room should not be created if its property is not included " + "in the available properties for its room type.", + ): + self.env["pms.room"].create( + { + "name": "Room 101", + "pms_property_id": self.pms_property3.id, + "room_type_id": self.room_type1.id, + } + ) + + def test_consistency_room_type_property(self): + """ + Room property and its room type properties are inconsistent. + A Room with property included in available properties + for its room type can be created. + """ + # ARRANGE + self.room_type1 = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id, self.pms_property2.id], + "name": "Single", + "default_code": "SI", + "class_id": self.room_type_class1.id, + "list_price": 30, + } + ) + # ACT + room1 = self.env["pms.room"].create( + { + "name": "Room 101", + "pms_property_id": self.pms_property1.id, + "room_type_id": self.room_type1.id, + } + ) + # ASSERT + self.assertIn( + room1.pms_property_id, + self.room_type1.pms_property_ids, + "The room should be created if its property is included " + "in the available properties for its room type.", + ) diff --git a/pms/tests/test_pms_payment.py b/pms/tests/test_pms_payment.py new file mode 100644 index 0000000000..79f42d4cb1 --- /dev/null +++ b/pms/tests/test_pms_payment.py @@ -0,0 +1,39 @@ +from freezegun import freeze_time + +from odoo.tests.common import SavepointCase + +freeze_time("2000-02-02") + + +class TestPmsPayment(SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + # TODO: Test allowed manual payment + # create a journal with allowed_pms_payments = True and + # check that the _get_payment_methods property method return it + + # TODO: Test not allowed manual payment + # create a journal without allowed_pms_payments = True and + # check that the _get_payment_methods property method dont return it + + # TODO: Test default account payment create + # create a bank journal, a reservation, pay the reservation + # with do_payment method without pay_type parameter + # and check that account payment was created + + # TODO: Test default statement line create + # create a cash journal, a reservation, pay the reservation + # with do_payment method without pay_type parameter + # and check that statement line was created + + # TODO: Test set pay_type cash, statement line create + # create a bank journal, a reservation, pay the reservation + # with do_payment method with 'cash' pay_type parameter + # and check that statement line was created + + # TODO: Test set pay_type bank, account payment create + # create a cash journal, a reservation, pay the reservation + # with do_payment method with 'bank' pay_type parameter + # and check that account payment was created diff --git a/pms/tests/test_pms_pricelist.py b/pms/tests/test_pms_pricelist.py new file mode 100644 index 0000000000..76bb8cfac4 --- /dev/null +++ b/pms/tests/test_pms_pricelist.py @@ -0,0 +1,1272 @@ +import datetime + +from freezegun import freeze_time + +from odoo import fields +from odoo.exceptions import ValidationError +from odoo.tests import tagged + +from .common import TestPms + + +@tagged("standard", "nice") +class TestPmsPricelist(TestPms): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.pms_property2 = cls.env["pms.property"].create( + { + "name": "Property_2", + "company_id": cls.env.ref("base.main_company").id, + "default_pricelist_id": cls.env.ref("product.list0").id, + } + ) + + cls.pms_property3 = cls.env["pms.property"].create( + { + "name": "Property_3", + "company_id": cls.env.ref("base.main_company").id, + "default_pricelist_id": cls.env.ref("product.list0").id, + } + ) + + cls.room_type1 = cls.env["pms.room.type"].create( + { + "pms_property_ids": [cls.pms_property1.id, cls.pms_property2.id], + "name": "Single", + "default_code": "SIN", + "class_id": cls.room_type_class1.id, + "list_price": 30, + } + ) + + # pms.room + cls.room1 = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property1.id, + "name": "Single 101", + "room_type_id": cls.room_type1.id, + "capacity": 2, + } + ) + + cls.pricelist2 = cls.env["product.pricelist"].create( + { + "name": "pricelist_2", + "pms_property_ids": [cls.pms_property1.id, cls.pms_property2.id], + "availability_plan_id": cls.availability_plan1.id, + "is_pms_available": True, + } + ) + # product.product 1 + cls.product1 = cls.env["product.product"].create({"name": "Test Breakfast"}) + + # pms.board.service + cls.board_service1 = cls.env["pms.board.service"].create( + { + "name": "Test Only Breakfast", + "default_code": "CB1", + } + ) + # pms.board.service.line + cls.board_service_line1 = cls.env["pms.board.service.line"].create( + { + "product_id": cls.product1.id, + "pms_board_service_id": cls.board_service1.id, + "adults": True, + } + ) + + # pms.board.service.room.type + cls.board_service_room_type1 = cls.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": cls.room_type1.id, + "pms_board_service_id": cls.board_service1.id, + "pms_property_id": cls.pms_property1.id, + } + ) + + cls.partner1 = cls.env["res.partner"].create({"name": "Carles"}) + + # create a sale channel + cls.sale_channel_direct1 = cls.env["pms.sale.channel"].create( + { + "name": "Door", + "channel_type": "direct", + } + ) + + @freeze_time("2000-01-01") + def test_board_service_pricelist_item_apply_sale_dates(self): + """ + Pricelist item is created to apply on board services at SALE date. + The reservation created take into account the board service + pricelist item created previously according to the SALE date. + """ + # ARRANGE + date_from = fields.date.today() + date_to = fields.date.today() + expected_price = 1000.0 + vals = { + "pricelist_id": self.pricelist2.id, + "date_start": datetime.datetime.combine( + date_from, datetime.datetime.min.time() + ), + "date_end": datetime.datetime.combine( + date_to, datetime.datetime.max.time() + ), + "compute_price": "fixed", + "applied_on": "0_product_variant", + "product_id": self.product1.id, + "board_service_room_type_id": self.board_service_room_type1.id, + "fixed_price": expected_price, + "pms_property_ids": [self.pms_property1.id], + } + self.env["product.pricelist.item"].create(vals) + # ACT + reservation_created = self.env["pms.reservation"].create( + { + "partner_id": self.partner1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "preferred_room_id": self.room1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist2.id, + "board_service_room_id": self.board_service_room_type1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + # ASSERT + self.assertEqual( + reservation_created.service_ids.price_subtotal, + expected_price, + "The reservation created should take into account the board service" + " pricelist item created previously according to the SALE date.", + ) + + @freeze_time("2000-01-01") + def test_board_service_pricelist_item_not_apply_sale_dates(self): + """ + Pricelist item is created to apply on board services at SALE date. + The reservation created DONT take into account the board service pricelist + item created previously according to the SALE date. + """ + # ARRANGE + date_from = fields.date.today() + datetime.timedelta(days=1) + date_to = fields.date.today() + datetime.timedelta(days=1) + not_expected_price = 1000.0 + vals = { + "pricelist_id": self.pricelist2.id, + "date_start": datetime.datetime.combine( + date_from, datetime.datetime.min.time() + ), + "date_end": datetime.datetime.combine( + date_to, datetime.datetime.max.time() + ), + "compute_price": "fixed", + "applied_on": "0_product_variant", + "product_id": self.product1.id, + "board_service_room_type_id": self.board_service_room_type1.id, + "fixed_price": not_expected_price, + "pms_property_ids": [self.pms_property1.id], + } + self.env["product.pricelist.item"].create(vals) + # ACT + reservation_created = self.env["pms.reservation"].create( + { + "partner_id": self.partner1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "preferred_room_id": self.room1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist2.id, + "board_service_room_id": self.board_service_room_type1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + # ASSERT + self.assertNotEqual( + reservation_created.service_ids.price_subtotal, + not_expected_price, + "The reservation created shouldnt take into account the board service pricelist" + " item created previously according to the SALE date.", + ) + + @freeze_time("2000-01-01") + def test_board_service_pricelist_item_apply_consumption_dates(self): + """ + Pricelist item is created to apply on board services + at CONSUMPTION date. + The reservation created take into account the board service + pricelist item created previously according to the CONSUMPTION date. + """ + # ARRANGE + date_from = fields.date.today() + datetime.timedelta(days=1) + date_to = fields.date.today() + datetime.timedelta(days=1) + expected_price = 1000.0 + vals = { + "pricelist_id": self.pricelist2.id, + "date_start_consumption": date_from, + "date_end_consumption": date_to, + "compute_price": "fixed", + "applied_on": "0_product_variant", + "product_id": self.product1.id, + "board_service_room_type_id": self.board_service_room_type1.id, + "fixed_price": expected_price, + "pms_property_ids": [self.pms_property1.id], + } + self.env["product.pricelist.item"].create(vals) + # ACT + reservation_created = self.env["pms.reservation"].create( + { + "partner_id": self.partner1.id, + "checkin": datetime.datetime.now() + datetime.timedelta(days=1), + "checkout": datetime.datetime.now() + datetime.timedelta(days=2), + "preferred_room_id": self.room1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist2.id, + "board_service_room_id": self.board_service_room_type1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + # ASSERT + self.assertEqual( + reservation_created.service_ids.price_subtotal, + expected_price, + "The reservation created should take into account the board service" + " pricelist item created previously according to the CONSUMPTION date.", + ) + + @freeze_time("2000-01-01") + def test_board_service_pricelist_item_not_apply_consumption_dates(self): + """ + Pricelist item is created to apply on board services + at CONSUMPTION date. + The reservation created DONT take into account the board service + pricelist item created previously according to the CONSUMPTION date. + """ + # ARRANGE + date_from = fields.date.today() + datetime.timedelta(days=2) + date_to = fields.date.today() + datetime.timedelta(days=2) + not_expected_price = 1000.0 + vals = { + "pricelist_id": self.pricelist2.id, + "date_start": datetime.datetime.combine( + date_from, datetime.datetime.min.time() + ), + "date_end": datetime.datetime.combine( + date_to, datetime.datetime.max.time() + ), + "compute_price": "fixed", + "applied_on": "0_product_variant", + "product_id": self.product1.id, + "board_service_room_type_id": self.board_service_room_type1.id, + "fixed_price": not_expected_price, + "pms_property_ids": [self.pms_property1.id], + } + self.env["product.pricelist.item"].create(vals) + # ACT + reservation_created = self.env["pms.reservation"].create( + { + "partner_id": self.partner1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "preferred_room_id": self.room1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist2.id, + "board_service_room_id": self.board_service_room_type1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + # ASSERT + self.assertNotEqual( + reservation_created.service_ids.price_subtotal, + not_expected_price, + "The reservation created shouldnt take into account the board service" + " pricelist item created previously according to the CONSUMPTION date.", + ) + + @freeze_time("2000-01-01") + def test_room_type_pricelist_item_apply_sale_dates(self): + """ + Pricelist item is created to apply on room types + at SALE date. + The reservation created take into account the room type + pricelist item created previously according to the SALE date. + """ + # ARRANGE + date_from = fields.date.today() + date_to = fields.date.today() + expected_price = 1000.0 + vals = { + "pricelist_id": self.pricelist2.id, + "date_start": datetime.datetime.combine( + date_from, datetime.datetime.min.time() + ), + "date_end": datetime.datetime.combine( + date_to, datetime.datetime.max.time() + ), + "compute_price": "fixed", + "applied_on": "0_product_variant", + "product_id": self.room_type1.product_id.id, + "fixed_price": expected_price, + "pms_property_ids": [self.pms_property1.id], + } + self.env["product.pricelist.item"].create(vals) + # ACT + reservation_created = self.env["pms.reservation"].create( + { + "partner_id": self.partner1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "preferred_room_id": self.room1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist2.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + # ASSERT + self.assertEqual( + reservation_created.price_subtotal, + expected_price, + "The reservation created should take into account the room type" + " pricelist item created previously according to the SALE date.", + ) + + @freeze_time("2000-01-01") + def test_room_type_pricelist_item_not_apply_sale_dates(self): + """ + Pricelist item is created to apply on room types + at SALE date. + The reservation created DONT take into account the room type + pricelist item created previously according to the SALE date. + """ + # ARRANGE + date_from = fields.date.today() + datetime.timedelta(days=1) + date_to = fields.date.today() + datetime.timedelta(days=1) + not_expected_price = 1000.0 + vals = { + "pricelist_id": self.pricelist2.id, + "date_start": datetime.datetime.combine( + date_from, datetime.datetime.min.time() + ), + "date_end": datetime.datetime.combine( + date_to, datetime.datetime.max.time() + ), + "compute_price": "fixed", + "applied_on": "0_product_variant", + "product_id": self.room_type1.product_id.id, + "fixed_price": not_expected_price, + "pms_property_ids": [self.pms_property1.id], + } + self.env["product.pricelist.item"].create(vals) + # ACT + reservation_created = self.env["pms.reservation"].create( + { + "partner_id": self.partner1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "preferred_room_id": self.room1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist2.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + # ASSERT + self.assertNotEqual( + reservation_created.price_subtotal, + not_expected_price, + "The reservation created shouldnt take into account the room type" + " pricelist item created previously according to the SALE date.", + ) + + @freeze_time("2000-01-01") + def test_room_type_pricelist_item_apply_consumption_dates(self): + """ + Pricelist item is created to apply on room types + at CONSUMPTION date. + The reservation created take into account the room type + pricelist item created previously according to the CONSUMPTION date. + """ + # ARRANGE + date_from = fields.date.today() + datetime.timedelta(days=1) + date_to = fields.date.today() + datetime.timedelta(days=1) + expected_price = 1000.0 + vals = { + "pricelist_id": self.pricelist2.id, + "date_start_consumption": date_from, + "date_end_consumption": date_to, + "compute_price": "fixed", + "applied_on": "0_product_variant", + "product_id": self.room_type1.product_id.id, + "fixed_price": expected_price, + "pms_property_ids": [self.pms_property1.id], + } + self.env["product.pricelist.item"].create(vals) + # ACT + reservation_created = self.env["pms.reservation"].create( + { + "partner_id": self.partner1.id, + "checkin": datetime.datetime.now() + datetime.timedelta(days=1), + "checkout": datetime.datetime.now() + datetime.timedelta(days=2), + "preferred_room_id": self.room1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist2.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + # ASSERT + self.assertEqual( + reservation_created.price_subtotal, + expected_price, + "The reservation created should take into account the room type" + " pricelist item created previously according to the CONSUMPTION date.", + ) + + @freeze_time("2000-01-01") + def test_room_type_pricelist_item_not_apply_consumption_dates(self): + """ + Pricelist item is created to apply on room types + at CONSUMPTION date. + The reservation created DONT take into account the room type + pricelist item created previously according to the CONSUMPTION date. + """ + # ARRANGE + date_from = fields.date.today() + datetime.timedelta(days=2) + date_to = fields.date.today() + datetime.timedelta(days=2) + not_expected_price = 1000.0 + vals = { + "pricelist_id": self.pricelist2.id, + "date_start": datetime.datetime.combine( + date_from, datetime.datetime.min.time() + ), + "date_end": datetime.datetime.combine( + date_to, datetime.datetime.max.time() + ), + "compute_price": "fixed", + "applied_on": "0_product_variant", + "product_id": self.room_type1.product_id.id, + "fixed_price": not_expected_price, + "pms_property_ids": [self.pms_property1.id], + } + self.env["product.pricelist.item"].create(vals) + # ACT + reservation_created = self.env["pms.reservation"].create( + { + "partner_id": self.partner1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "preferred_room_id": self.room1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist2.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + # ASSERT + self.assertNotEqual( + reservation_created.price_subtotal, + not_expected_price, + "The reservation created shouldnt take into account the room type" + " pricelist item created previously according to the CONSUMPTION date.", + ) + + @freeze_time("2000-01-01") + def test_service_pricelist_item_apply_sale_dates(self): + """ + Pricelist item is created to apply on services at SALE date. + The reservation created take into account the service + pricelist item created previously according to the SALE date. + """ + # ARRANGE + date_from = fields.date.today() + date_to = fields.date.today() + expected_price = 1000.0 + vals = { + "pricelist_id": self.pricelist2.id, + "date_start": datetime.datetime.combine( + date_from, datetime.datetime.min.time() + ), + "date_end": datetime.datetime.combine( + date_to, datetime.datetime.max.time() + ), + "compute_price": "fixed", + "applied_on": "0_product_variant", + "product_id": self.product1.id, + "fixed_price": expected_price, + "pms_property_ids": [self.pms_property1.id], + } + self.env["product.pricelist.item"].create(vals) + # ACT + reservation_created = self.env["pms.reservation"].create( + { + "partner_id": self.partner1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "preferred_room_id": self.room1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist2.id, + "service_ids": [(0, 0, {"product_id": self.product1.id})], + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + # ASSERT + self.assertEqual( + reservation_created.service_ids.price_subtotal, + expected_price, + "The reservation created should take into account the service" + " pricelist item created previously according to the SALE date.", + ) + + @freeze_time("2000-01-01") + def test_service_pricelist_item_not_apply_sale_dates(self): + """ + Pricelist item is created to apply on services at SALE date. + The reservation created DONT take into account the service pricelist + item created previously according to the SALE date. + """ + # ARRANGE + date_from = fields.date.today() + datetime.timedelta(days=1) + date_to = fields.date.today() + datetime.timedelta(days=1) + not_expected_price = 1000.0 + vals = { + "pricelist_id": self.pricelist2.id, + "date_start": datetime.datetime.combine( + date_from, datetime.datetime.min.time() + ), + "date_end": datetime.datetime.combine( + date_to, datetime.datetime.max.time() + ), + "compute_price": "fixed", + "applied_on": "0_product_variant", + "product_id": self.product1.id, + "fixed_price": not_expected_price, + "pms_property_ids": [self.pms_property1.id], + } + self.env["product.pricelist.item"].create(vals) + # ACT + reservation_created = self.env["pms.reservation"].create( + { + "partner_id": self.partner1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "preferred_room_id": self.room1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist2.id, + "service_ids": [(0, 0, {"product_id": self.product1.id})], + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + # ASSERT + self.assertNotEqual( + reservation_created.service_ids.price_subtotal, + not_expected_price, + "The reservation created shouldnt take into account the service pricelist" + " item created previously according to the SALE date.", + ) + + @freeze_time("2000-01-01") + def test_service_pricelist_item_apply_consumption_dates(self): + """ + Pricelist item is created to apply on services at CONSUMPTION date. + The reservation created take into account the service + pricelist item created previously according to the CONSUMPTION date. + """ + # ARRANGE + date_from = fields.date.today() + datetime.timedelta(days=1) + date_to = fields.date.today() + datetime.timedelta(days=1) + expected_price = 1000.0 + vals = { + "pricelist_id": self.pricelist2.id, + "date_start_consumption": date_from, + "date_end_consumption": date_to, + "compute_price": "fixed", + "applied_on": "0_product_variant", + "product_id": self.product1.id, + "fixed_price": expected_price, + "pms_property_ids": [self.pms_property1.id], + } + self.env["product.pricelist.item"].create(vals) + # ACT + reservation_created = self.env["pms.reservation"].create( + { + "partner_id": self.partner1.id, + "checkin": datetime.datetime.now() + datetime.timedelta(days=1), + "checkout": datetime.datetime.now() + datetime.timedelta(days=2), + "preferred_room_id": self.room1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist2.id, + "service_ids": [(0, 0, {"product_id": self.product1.id})], + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + # ASSERT + self.assertEqual( + reservation_created.service_ids.price_subtotal, + expected_price, + "The reservation created should take into account the service" + " pricelist item created previously according to the CONSUMPTION date.", + ) + + @freeze_time("2000-01-01") + def test_service_pricelist_item_not_apply_consumption_dates(self): + """ + Pricelist item is created to apply on services at CONSUMPTION date. + The reservation created DONT take into account the service pricelist + item created previously according to the CONSUMPTION date. + """ + # ARRANGE + date_from = fields.date.today() + datetime.timedelta(days=2) + date_to = fields.date.today() + datetime.timedelta(days=2) + not_expected_price = 1000.0 + vals = { + "pricelist_id": self.pricelist2.id, + "date_start": datetime.datetime.combine( + date_from, datetime.datetime.min.time() + ), + "date_end": datetime.datetime.combine( + date_to, datetime.datetime.max.time() + ), + "compute_price": "fixed", + "applied_on": "0_product_variant", + "product_id": self.product1.id, + "fixed_price": not_expected_price, + "pms_property_ids": [self.pms_property1.id], + } + self.env["product.pricelist.item"].create(vals) + # ACT + reservation_created = self.env["pms.reservation"].create( + { + "partner_id": self.partner1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "preferred_room_id": self.room1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist2.id, + "service_ids": [(0, 0, {"product_id": self.product1.id})], + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + # ASSERT + self.assertNotEqual( + reservation_created.service_ids.price_subtotal, + not_expected_price, + "The reservation created shouldnt take into account the service pricelist " + "item created previously according to the CONSUMPTION date.", + ) + + @freeze_time("2000-01-01") + def _test_inconsistencies_pricelist_daily(self): + """ + Test cases to verify that a daily pricelist cannot be created because: + (Test case1): item has two properties and a items daily pricelist only + can has a one property. + (Test case2): item has all properties(pms_property_ids = False indicates + all properties)and a items daily pricelist only can has a one property. + (Test case3): item compute_price is 'percentage' and only can be 'fixed' + for items daily pricelist. + (Test case4): item compute_price is 'percentage' and has two properties + but compute_price can only be fixed and can only have one + property for items daily pricelist. + (Test case5): item compute_price is 'percentage' and has all properties + (pms_property_ids = False indicates all properties)but + compute_pricecan only be fixed and can only have one property for + items daily pricelist. + (Test case6): The difference of days between date_start_consumption and + date_end_consumption is three and the items of a daily pricelist + can only be one. + """ + test_cases = [ + { + "compute_price": "fixed", + "pms_property_ids": [self.pms_property1.id, self.pms_property2.id], + "date_start_consumption": datetime.datetime.now(), + "date_end_consumption": datetime.datetime.today() + + datetime.timedelta(days=1), + }, + { + "compute_price": "fixed", + "pms_property_ids": False, + "date_start_consumption": datetime.datetime.now(), + "date_end_consumption": datetime.datetime.today() + + datetime.timedelta(days=1), + }, + { + "compute_price": "percentage", + "pms_property_ids": [self.pms_property1.id], + "date_start_consumption": datetime.datetime.now(), + "date_end_consumption": datetime.datetime.today() + + datetime.timedelta(days=1), + }, + { + "compute_price": "percentage", + "pms_property_ids": [self.pms_property1.id, self.pms_property2.id], + "date_start_consumption": datetime.datetime.now(), + "date_end_consumption": datetime.datetime.today() + + datetime.timedelta(days=1), + }, + { + "compute_price": "percentage", + "pms_property_ids": False, + "date_start_consumption": datetime.datetime.now(), + "date_end_consumption": datetime.datetime.today() + + datetime.timedelta(days=1), + }, + { + "compute_price": "fixed", + "pms_property_ids": [self.pms_property1.id], + "date_start_consumption": datetime.datetime.now(), + "date_end_consumption": datetime.datetime.today() + + datetime.timedelta(days=3), + }, + ] + + for tc in test_cases: + with self.subTest(k=tc): + with self.assertRaises( + ValidationError, + msg="Item only can has one property, the compute price only can" + "be fixed and the difference between date_start_consumption" + "and date_end_consumption only can be 1", + ): + self.room_type1.pms_property_ids = tc["pms_property_ids"] + item = self.env["product.pricelist.item"].create( + { + "pms_property_ids": tc["pms_property_ids"], + "compute_price": tc["compute_price"], + "applied_on": "0_product_variant", + "product_id": self.room_type1.product_id.id, + "date_start_consumption": tc["date_start_consumption"], + "date_end_consumption": tc["date_end_consumption"], + } + ) + self.pricelist_test = self.env["product.pricelist"].create( + { + "name": "Pricelist test", + "pricelist_type": "daily", + "pms_property_ids": tc["pms_property_ids"], + "item_ids": [item.id], + "availability_plan_id": self.availability_plan1.id, + "is_pms_available": True, + } + ) + + @freeze_time("2020-01-01") + def test_consistency_pricelist_daily(self): + """ + Test to verify that a daily pricelist is created. + Create a pricelist item with a property, the value of compute_price is + fixed and date_start_consumption date_end_consumption has the same value + """ + self.room_type1.pms_property_ids = (self.pms_property1.id,) + item = self.env["product.pricelist.item"].create( + { + "pms_property_ids": [self.pms_property1.id], + "compute_price": "fixed", + "applied_on": "0_product_variant", + "product_id": self.room_type1.product_id.id, + "date_start_consumption": datetime.date.today(), + "date_end_consumption": datetime.date.today(), + } + ) + self.pricelist_test = self.env["product.pricelist"].create( + { + "name": "Pricelist test", + "pricelist_type": "daily", + "pms_property_ids": [self.pms_property1.id], + "item_ids": [item.id], + "availability_plan_id": self.availability_plan1.id, + "is_pms_available": True, + } + ) + self.assertTrue(self.pricelist_test, "Pricelist not created.") + + @freeze_time("2000-01-01") + def test_simple_price_without_items(self): + """ + Test case for no items applied in a reservation. + """ + + # ARRANGE + self.room_type = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id, self.pms_property2.id], + "name": "Single", + "default_code": "S", + "class_id": self.room_type_class1.id, + "list_price": 30, + } + ) + + self.room = self.env["pms.room"].create( + { + "pms_property_id": self.pms_property1.id, + "name": "Single 1", + "room_type_id": self.room_type.id, + } + ) + reservation = self.env["pms.reservation"].create( + { + "checkin": datetime.datetime.today(), + "checkout": datetime.datetime.today() + datetime.timedelta(days=3), + "preferred_room_id": self.room.id, + "pms_property_id": self.pms_property1.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + # ACT + n_days = (reservation.checkout - reservation.checkin).days + expected_price = self.room.room_type_id.list_price * n_days + + # ASSERT + self.assertEqual( + expected_price, reservation.price_subtotal, "The price is not as expected" + ) + + @freeze_time("2022-01-01") + def test_items_sort(self): + """ + Test cases to verify the order for each field considered individually + Test cases to prioritize fields over other fields: + 1. applied_on + 2. date + 3. date consumption + 4. num. properties + 5. id + - tie + - no [date_start|date_end|date_start_consumption|date_end_consumption] + """ + # ARRANGE + self.product_category = self.env["product.category"].create( + {"name": "Category1"} + ) + self.product_template = self.env["product.template"].create( + {"name": "Template1"} + ) + self.room_type = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id, self.pms_property2.id], + "name": "Single", + "default_code": "SGL", + "class_id": self.room_type_class1.id, + "list_price": 30, + } + ) + + self.room = self.env["pms.room"].create( + { + "pms_property_id": self.pms_property1.id, + "name": "101", + "room_type_id": self.room_type.id, + } + ) + properties = self.room_type.product_id.pms_property_ids.ids + test_cases = [ + { + "name": "sorting applied_on", + "expected_price": 50 * 3, + "items": [ + { + "pricelist_id": self.pricelist1.id, + "applied_on": "2_product_category", + "categ_id": self.product_category.id, + "product_id": self.room_type.product_id.id, + "fixed_price": 60.0, + }, + { + "pricelist_id": self.pricelist1.id, + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "fixed_price": 50.0, + "pms_property_ids": properties, + }, + { + "pricelist_id": self.pricelist1.id, + "applied_on": "1_product", + "product_id": self.room_type.product_id.id, + "product_tmpl_id": self.product_template.id, + "fixed_price": 40.0, + "pms_property_ids": properties, + }, + ], + }, + { + "name": "sorting SALE date min range", + "expected_price": 50.0 * 3, + "items": [ + { + "pricelist_id": self.pricelist1.id, + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "date_start": datetime.datetime.now(), + "date_end": datetime.datetime.now() + + datetime.timedelta(days=2), + "fixed_price": 60.0, + "pms_property_ids": properties, + }, + { + "pricelist_id": self.pricelist1.id, + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "date_start": datetime.datetime.now(), + "date_end": datetime.datetime.now() + + datetime.timedelta(days=1), + "fixed_price": 50.0, + "pms_property_ids": properties, + }, + { + "pricelist_id": self.pricelist1.id, + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "date_start": datetime.datetime.now(), + "date_end": datetime.datetime.now() + + datetime.timedelta(days=3), + "fixed_price": 40.0, + "pms_property_ids": properties, + }, + ], + }, + { + "name": "sorting CONSUMPTION date min range", + "expected_price": 40.0 * 3, + "items": [ + { + "pricelist_id": self.pricelist1.id, + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "date_start_consumption": datetime.datetime.now(), + "date_end_consumption": datetime.datetime.now() + + datetime.timedelta(days=6), + "fixed_price": 60.0, + "pms_property_ids": properties, + }, + { + "pricelist_id": self.pricelist1.id, + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "date_start_consumption": datetime.datetime.now(), + "date_end_consumption": datetime.datetime.now() + + datetime.timedelta(days=10), + "fixed_price": 50.0, + "pms_property_ids": properties, + }, + { + "pricelist_id": self.pricelist1.id, + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "date_start_consumption": datetime.datetime.now(), + "date_end_consumption": datetime.datetime.now() + + datetime.timedelta(days=3), + "fixed_price": 40.0, + "pms_property_ids": properties, + }, + ], + }, + { + "name": "sorting num. properties", + "expected_price": 50.0 * 3, + "items": [ + { + "pricelist_id": self.pricelist1.id, + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "fixed_price": 60.0, + "pms_property_ids": properties, + }, + { + "pricelist_id": self.pricelist1.id, + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "pms_property_ids": [self.pms_property1.id], + "fixed_price": 50.0, + }, + { + "pricelist_id": self.pricelist1.id, + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "pms_property_ids": [ + self.pms_property1.id, + self.pms_property2.id, + ], + "fixed_price": 40.0, + }, + ], + }, + { + "name": "sorting by item id", + "expected_price": 40.0 * 3, + "items": [ + { + "pricelist_id": self.pricelist1.id, + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "fixed_price": 60.0, + "pms_property_ids": properties, + }, + { + "pricelist_id": self.pricelist1.id, + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "fixed_price": 50.0, + "pms_property_ids": properties, + }, + { + "pricelist_id": self.pricelist1.id, + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "fixed_price": 40.0, + "pms_property_ids": properties, + }, + ], + }, + { + "name": "prioritize applied_on over SALE date", + "expected_price": 60.0 * 3, + "items": [ + { + "pricelist_id": self.pricelist1.id, + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "date_start": datetime.datetime.now(), + "date_end": datetime.datetime.now() + + datetime.timedelta(days=2), + "fixed_price": 60.0, + "pms_property_ids": properties, + }, + { + "pricelist_id": self.pricelist1.id, + "product_id": self.room_type.product_id.id, + "product_tmpl_id": self.product_template.id, + "applied_on": "1_product", + "date_start": datetime.datetime.now(), + "date_end": datetime.datetime.now() + + datetime.timedelta(days=1), + "fixed_price": 50.0, + "pms_property_ids": properties, + }, + ], + }, + { + "name": "prioritize SALE date over CONSUMPTION date", + "expected_price": 120.0 * 3, + "items": [ + { + "pricelist_id": self.pricelist1.id, + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "date_start": datetime.datetime.now(), + "date_end": datetime.datetime.now() + + datetime.timedelta(days=10), + "fixed_price": 120.0, + "pms_property_ids": properties, + }, + { + "pricelist_id": self.pricelist1.id, + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "date_start_consumption": datetime.datetime.now(), + "date_end_consumption": datetime.datetime.now() + + datetime.timedelta(days=3), + "fixed_price": 50.0, + "pms_property_ids": properties, + }, + ], + }, + { + "name": "prioritize CONSUMPTION date over min. num. properties", + "expected_price": 50.0 * 3, + "items": [ + { + "pricelist_id": self.pricelist1.id, + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "date_start_consumption": datetime.datetime.now(), + "date_end_consumption": datetime.datetime.now() + + datetime.timedelta(days=3), + "fixed_price": 120.0, + "pms_property_ids": properties, + }, + { + "pricelist_id": self.pricelist1.id, + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "date_start_consumption": datetime.datetime.now(), + "date_end_consumption": datetime.datetime.now() + + datetime.timedelta(days=3), + "pms_property_ids": [ + self.pms_property1.id, + self.pms_property2.id, + ], + "fixed_price": 50.0, + }, + ], + }, + { + "name": "prioritize min. num. properties over item id", + "expected_price": 50.0 * 3, + "items": [ + { + "pricelist_id": self.pricelist1.id, + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "date_start_consumption": datetime.datetime.now(), + "date_end_consumption": datetime.datetime.now() + + datetime.timedelta(days=3), + "fixed_price": 120.0, + "pms_property_ids": properties, + }, + { + "pricelist_id": self.pricelist1.id, + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "date_start_consumption": datetime.datetime.now(), + "date_end_consumption": datetime.datetime.now() + + datetime.timedelta(days=3), + "pms_property_ids": [ + self.pms_property1.id, + self.pms_property2.id, + ], + "fixed_price": 50.0, + }, + ], + }, + { + "name": "tie => order by item id", + "expected_price": 50.0 * 3, + "items": [ + { + "pricelist_id": self.pricelist1.id, + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "date_start_consumption": datetime.datetime.now(), + "date_end_consumption": datetime.datetime.now() + + datetime.timedelta(days=3), + "date_start": datetime.datetime.now(), + "date_end": datetime.datetime.now() + + datetime.timedelta(days=3), + "pms_property_ids": [ + self.pms_property1.id, + self.pms_property2.id, + ], + "fixed_price": 120.0, + }, + { + "pricelist_id": self.pricelist1.id, + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "date_start_consumption": datetime.datetime.now(), + "date_end_consumption": datetime.datetime.now() + + datetime.timedelta(days=3), + "date_start": datetime.datetime.now(), + "date_end": datetime.datetime.now() + + datetime.timedelta(days=3), + "pms_property_ids": [ + self.pms_property1.id, + self.pms_property2.id, + ], + "fixed_price": 50.0, + }, + ], + }, + { + "name": "no SALE DATE START", + "expected_price": 40.0 * 3, + "items": [ + { + "pricelist_id": self.pricelist1.id, + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "date_end": datetime.datetime.now() + + datetime.timedelta(days=1), + "fixed_price": 40.0, + "pms_property_ids": properties, + }, + ], + }, + { + "name": "no SALE DATE END", + "expected_price": 40.0 * 3, + "items": [ + { + "pricelist_id": self.pricelist1.id, + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "date_start": datetime.datetime.now(), + "fixed_price": 40.0, + "pms_property_ids": properties, + }, + ], + }, + { + "name": "no consumption DATE START", + "expected_price": 40.0 + self.room_type.list_price * 2, + "items": [ + { + "pricelist_id": self.pricelist1.id, + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "date_end_consumption": datetime.datetime.now(), + "fixed_price": 40.0, + "pms_property_ids": properties, + }, + ], + }, + { + "name": "no consumption DATE END", + "expected_price": 40.0 * 3, + "items": [ + { + "pricelist_id": self.pricelist1.id, + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "date_start_consumption": datetime.datetime.now(), + "fixed_price": 40.0, + "pms_property_ids": properties, + }, + ], + }, + { + "name": "only applied consumption in one night", + "expected_price": 40.0 + self.room_type.list_price * 2, + "items": [ + { + "pricelist_id": self.pricelist1.id, + "applied_on": "0_product_variant", + "product_id": self.room_type.product_id.id, + "date_start_consumption": datetime.datetime.now(), + "date_end_consumption": datetime.datetime.now(), + "fixed_price": 40.0, + "pms_property_ids": properties, + }, + ], + }, + ] + + for tc in test_cases: + with self.subTest(k=tc): + + # ARRANGE + items = [] + for item in tc["items"]: + item = self.env["product.pricelist.item"].create(item) + items.append(item.id) + + # ACT + reservation = self.env["pms.reservation"].create( + { + "partner_id": self.partner1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + + datetime.timedelta(days=3), + "preferred_room_id": self.room.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + reservation_price = reservation.price_subtotal + self.env["pms.reservation"].browse(reservation.id).unlink() + self.env["product.pricelist.item"].browse(items).unlink() + + # ASSERT + self.assertEqual(tc["expected_price"], reservation_price, tc["name"]) diff --git a/pms/tests/test_pms_pricelist_settings.py b/pms/tests/test_pms_pricelist_settings.py new file mode 100644 index 0000000000..7b04ebc96a --- /dev/null +++ b/pms/tests/test_pms_pricelist_settings.py @@ -0,0 +1,55 @@ +from odoo.exceptions import ValidationError + +from .common import TestPms + + +class TestPmsPricelistSettings(TestPms): + def test_advanced_pricelist_exists(self): + """ + Check if value of Pricelist parameter in sales settings is Advanced Price Rules. + Find the value of Pricelist parameter + with the key product.product_pricelist_setting and check if is equal to "advanced". + """ + # ARRANGE + key = "product.product_pricelist_setting" + value = "advanced" + + # ACT + found_value = self.env["ir.config_parameter"].sudo().get_param(key) + + # ASSERT + self.assertEqual( + found_value, value, "Parameter of Pricelist in setting is not 'advanced'" + ) + + def test_product_pricelist_setting_not_modified(self): + """ + Check that Pricelist parameter 'advanced' cannot be modified. + Set the value of product.product_pricelist_setting to 'basic' + but is not possible because this only can be 'advanced'. + """ + # ARRANGE + key = "product.product_pricelist_setting" + value = "basic" + + # ACT & ASSERT + with self.assertRaises( + ValidationError, msg="The Pricelist parameter 'advanced' was modified." + ): + self.env["ir.config_parameter"].set_param(key, value) + + def test_product_pricelist_setting_not_unlink(self): + """ + Check that Pricelist parameter 'advanced' cannot be unlink. + Try to unlink the parameter product_pricelist with value 'advanced' + but this should be impossible. + """ + # ARRANGE + key = "product.product_pricelist_setting" + value = "advanced" + + # ACT & ASSERT + with self.assertRaises(ValidationError), self.cr.savepoint(): + self.env["ir.config_parameter"].search( + [("key", "=", key), ("value", "=", value)] + ).unlink() diff --git a/pms/tests/test_pms_res_users.py b/pms/tests/test_pms_res_users.py new file mode 100644 index 0000000000..12fb0b69a4 --- /dev/null +++ b/pms/tests/test_pms_res_users.py @@ -0,0 +1,165 @@ +from odoo.exceptions import ValidationError + +from .common import TestPms + + +class TestPmsResUser(TestPms): + @classmethod + def setUpClass(cls): + super().setUpClass() + # create a company and properties + cls.company_A = cls.env["res.company"].create( + { + "name": "Pms_Company1", + } + ) + cls.company_B = cls.env["res.company"].create( + { + "name": "Pms_Company2", + } + ) + cls.property_A1 = cls.env["pms.property"].create( + { + "name": "Pms_property", + "company_id": cls.company_A.id, + "default_pricelist_id": cls.pricelist1.id, + } + ) + cls.property_A2 = cls.env["pms.property"].create( + { + "name": "Pms_property2", + "company_id": cls.company_A.id, + "default_pricelist_id": cls.pricelist1.id, + } + ) + cls.property_B1 = cls.env["pms.property"].create( + { + "name": "Pms_propertyB1", + "company_id": cls.company_B.id, + "default_pricelist_id": cls.pricelist1.id, + } + ) + + def test_property_not_in_allowed_properties(self): + """ + Property not allowed for the user + Check a user cannot have an active property + that is not in the allowed properties + + Company_A ---> Property_A1, Property_A2 + Company_B ---> Property_B1 + + + """ + # ARRANGE + Users = self.env["res.users"] + # ACT & ASSERT + with self.assertRaises( + ValidationError, + msg="Some property is not included in the allowed properties", + ): + Users.create( + { + "name": "Test User", + "login": "test_user", + "company_ids": [(4, self.company_A.id)], + "company_id": self.company_A.id, + "pms_property_ids": [(4, self.property_A1.id)], + "pms_property_id": self.property_B1.id, + } + ) + + def test_property_not_in_allowed_companies(self): + """ + Property not allowed for the user + Check a user cannot have a property in allowed properties + that does not belong to their companies + + Company_A ---> Property_A1, Property_A2 + Company_B ---> Property_B1 + + """ + # ARRANGE + Users = self.env["res.users"] + # ACT & ASSERT + with self.assertRaises( + ValidationError, msg="Some property doesn't belong to the allowed companies" + ): + Users.create( + { + "name": "Test User", + "login": "test_user", + "company_ids": [(4, self.company_A.id)], + "company_id": self.company_A.id, + "pms_property_ids": [ + (4, self.property_A1.id), + (4, self.property_B1.id), + ], + "pms_property_id": self.property_A1.id, + } + ) + + def test_property_in_allowed_properties(self): + """ + Successful user creation + Check user creation with active property in allowed properties + + Company_A ---> Property_A1, Property_A2 + Company_B ---> Property_B1 + + """ + # ARRANGE + Users = self.env["res.users"] + # ACT + user1 = Users.create( + { + "name": "Test User", + "login": "test_user", + "company_ids": [(4, self.company_A.id)], + "company_id": self.company_A.id, + "pms_property_ids": [ + (4, self.property_A1.id), + (4, self.property_A2.id), + ], + "pms_property_id": self.property_A1.id, + } + ) + # ASSERT + self.assertIn( + user1.pms_property_id, + user1.pms_property_ids, + "Active property not in allowed properties", + ) + + def test_properties_belong_to_companies(self): + """ + Successful user creation + Check user creation with active property and allowed properties + belonging to the allowed companies + + Company_A ---> Property_A1, Property_A2 + Company_B ---> Property_B1 + + """ + # ARRANGE + Users = self.env["res.users"] + # ACT + user1 = Users.create( + { + "name": "Test User", + "login": "test_user", + "company_ids": [(4, self.company_A.id)], + "company_id": self.company_A.id, + "pms_property_ids": [ + (4, self.property_A1.id), + (4, self.property_A2.id), + ], + "pms_property_id": self.property_A1.id, + } + ) + # ASSERT + self.assertEqual( + user1.pms_property_id.company_id, + user1.company_id, + "Active property doesn't belong to active company", + ) diff --git a/pms/tests/test_pms_reservation.py b/pms/tests/test_pms_reservation.py new file mode 100644 index 0000000000..fb7ce2e730 --- /dev/null +++ b/pms/tests/test_pms_reservation.py @@ -0,0 +1,4235 @@ +import datetime + +from freezegun import freeze_time + +from odoo import fields +from odoo.exceptions import UserError, ValidationError + +from .common import TestPms + + +class TestPmsReservations(TestPms): + @classmethod + def setUpClass(cls): + super().setUpClass() + # create a room type availability + cls.room_type_availability = cls.env["pms.availability.plan"].create( + { + "name": "Availability plan for TEST", + "pms_pricelist_ids": [(6, 0, [cls.pricelist1.id])], + } + ) + + # create room type + cls.room_type_double = cls.env["pms.room.type"].create( + { + "pms_property_ids": [cls.pms_property1.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": cls.room_type_class1.id, + } + ) + + cls.room_type_triple = cls.env["pms.room.type"].create( + { + "pms_property_ids": [cls.pms_property1.id], + "name": "Triple Test", + "default_code": "TRP_Test", + "class_id": cls.room_type_class1.id, + } + ) + + # create rooms + cls.room1 = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property1.id, + "name": "Double 101", + "room_type_id": cls.room_type_double.id, + "capacity": 2, + "extra_beds_allowed": 1, + } + ) + + cls.room2 = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property1.id, + "name": "Double 102", + "room_type_id": cls.room_type_double.id, + "capacity": 2, + "extra_beds_allowed": 1, + } + ) + + cls.room3 = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property1.id, + "name": "Double 103", + "room_type_id": cls.room_type_double.id, + "capacity": 2, + "extra_beds_allowed": 1, + } + ) + + cls.room4 = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property1.id, + "name": "Triple 104", + "room_type_id": cls.room_type_triple.id, + "capacity": 3, + "extra_beds_allowed": 1, + } + ) + cls.partner1 = cls.env["res.partner"].create( + { + "firstname": "Jaime", + "lastname": "García", + "email": "jaime@example.com", + "birthdate_date": "1983-03-01", + "gender": "male", + } + ) + cls.id_category = cls.env["res.partner.id_category"].create( + {"name": "DNI", "code": "D"} + ) + cls.sale_channel_direct = cls.env["pms.sale.channel"].create( + {"name": "sale channel direct", "channel_type": "direct"} + ) + cls.sale_channel1 = cls.env["pms.sale.channel"].create( + {"name": "saleChannel1", "channel_type": "indirect"} + ) + cls.agency1 = cls.env["res.partner"].create( + { + "name": "partner1", + "is_agency": True, + "invoice_to_agency": "always", + "default_commission": 15, + "sale_channel_id": cls.sale_channel1.id, + } + ) + + @freeze_time("2012-01-14") + def test_reservation_dates_not_consecutive(self): + """ + Check the constrain if not consecutive dates + ---------------- + Create correct reservation set 3 reservation lines consecutives (nights) + """ + # ARRANGE + today = fields.date.today() + tomorrow = fields.date.today() + datetime.timedelta(days=1) + three_days_later = fields.date.today() + datetime.timedelta(days=3) + + # ACT & ASSERT + with self.assertRaises( + ValidationError, + msg="Error, it has been allowed to create a reservation with non-consecutive days", + ): + self.env["pms.reservation"].create( + { + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "reservation_line_ids": [ + (0, False, {"date": today}), + (0, False, {"date": tomorrow}), + (0, False, {"date": three_days_later}), + ], + } + ) + + @freeze_time("2012-01-14") + def test_reservation_dates_compute_checkin_out(self): + """ + Check the reservation creation with specific reservation lines + anc compute checkin checkout + ---------------- + Create reservation with correct reservation lines and check + the checkin and checkout fields. Take into account that the + checkout of the reservation must be the day after the last night + (view checkout assertEqual) + """ + # ARRANGE + today = fields.date.today() + tomorrow = fields.date.today() + datetime.timedelta(days=1) + two_days_later = fields.date.today() + datetime.timedelta(days=2) + + # ACT + reservation = self.env["pms.reservation"].create( + { + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + "reservation_line_ids": [ + (0, False, {"date": today}), + (0, False, {"date": tomorrow}), + (0, False, {"date": two_days_later}), + ], + } + ) + + # ASSERT + self.assertEqual( + reservation.checkin, + today, + "The calculated checkin of the reservation does \ + not correspond to the first day indicated in the dates", + ) + self.assertEqual( + reservation.checkout, + two_days_later + datetime.timedelta(days=1), + "The calculated checkout of the reservation does \ + not correspond to the last day indicated in the dates", + ) + + @freeze_time("2012-01-14") + def test_create_reservation_start_date(self): + """ + Check that the reservation checkin and the first reservation date are equal. + ---------------- + Create a reservation and check if the first reservation line date are the same + date that the checkin date. + """ + # reservation should start on checkin day + + # ARRANGE + today = fields.date.today() + checkin = today + datetime.timedelta(days=8) + checkout = checkin + datetime.timedelta(days=11) + reservation_vals = { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + + # ACT + reservation = self.env["pms.reservation"].create(reservation_vals) + + self.assertEqual( + reservation.reservation_line_ids[0].date, + checkin, + "Reservation lines don't start in the correct date", + ) + + @freeze_time("2012-01-14") + def test_create_reservation_end_date(self): + """ + Check that the reservation checkout and the last reservation date are equal. + ---------------- + Create a reservation and check if the last reservation line date are the same + date that the checkout date. + """ + # ARRANGE + today = fields.date.today() + checkin = today + datetime.timedelta(days=8) + checkout = checkin + datetime.timedelta(days=11) + reservation_vals = { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + + # ACT + reservation = self.env["pms.reservation"].create(reservation_vals) + + self.assertEqual( + reservation.reservation_line_ids[-1].date, + checkout - datetime.timedelta(1), + "Reservation lines don't end in the correct date", + ) + + @freeze_time("2012-01-14") + def test_split_reservation01(self): + """ + # TEST CASE + The reservation shouldn't be splitted + preferred_room_id with availability provided + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | test | test | test | | | | + | Double 102 | | | | | | | + | Double 103 | | | | | | | + +------------+------+------+------+----+----+----+ + """ + # ARRANGE + + # ACT + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room1.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + r_test.flush() + + # ASSERT + self.assertTrue( + all( + elem.room_id.id == r_test.reservation_line_ids[0].room_id.id + for elem in r_test.reservation_line_ids + ), + "The entire reservation should be allocated in the preferred room", + ) + + @freeze_time("2012-01-14") + def test_split_reservation02(self): + """ + # TEST CASE + The reservation shouldn't be splitted + room_type_id with availability provided + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | test | test | test | | | | + | Double 102 | | | | | | | + | Double 103 | | | | | | | + +------------+------+------+------+----+----+----+ + """ + # ARRANGE + + # ACT + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=2), + "adults": 2, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + r_test.flush() + + # ASSERT + self.assertFalse(r_test.splitted, "The reservation shouldn't be splitted") + + def test_split_reservation03(self): + """ + # TEST CASE + The reservation should be splitted in 2 rooms + (there is only one better option on day 02 and a draw the next day. + The night before should be prioritized) + +------------+------+------+------+------+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+------+----+----+ + | Double 101 | test | r3 | | | | | + | Double 102 | r1 | test | test | test | | | + | Double 103 | r2 | r4 | | | | | + +------------+------+------+------+------+----+----+ + """ + # ARRANGE + + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + r1.reservation_line_ids[0].room_id = self.room2.id + r1.flush() + + r2 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + r2.reservation_line_ids[0].room_id = self.room3.id + r2.flush() + + r3 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now() + datetime.timedelta(days=1), + "checkout": datetime.datetime.now() + datetime.timedelta(days=2), + "adults": 2, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + r3.reservation_line_ids[0].room_id = self.room1.id + r3.flush() + + r4 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now() + datetime.timedelta(days=1), + "checkout": datetime.datetime.now() + datetime.timedelta(days=2), + "adults": 2, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + r4.reservation_line_ids[0].room_id = self.room3.id + r4.flush() + expected_num_changes = 2 + + # ACT + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=4), + "adults": 2, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + r_test.flush() + # ASSERT + self.assertEqual( + expected_num_changes, + len(r_test.reservation_line_ids.mapped("room_id")), + "The reservation shouldn't have more than 2 changes", + ) + + @freeze_time("2012-01-14") + def test_split_reservation04(self): + """ + # TEST CASE + The reservation should be splitted in 3 rooms + (there are 2 best options on day 03 and room of last night is not available) + +------------+------+------+------+------+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+------+----+----+ + | Double 101 | test | r3 | test | test | | | + | Double 102 | r1 | test | r5 | | | | + | Double 103 | r2 | r4 | | | | | + +------------+------+------+------+------+----+----+ + """ + # ARRANGE + + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + r1.reservation_line_ids[0].room_id = self.room2.id + r1.flush() + + r2 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + r2.reservation_line_ids[0].room_id = self.room3.id + r2.flush() + + r3 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now() + datetime.timedelta(days=1), + "checkout": datetime.datetime.now() + datetime.timedelta(days=2), + "adults": 2, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + r3.reservation_line_ids[0].room_id = self.room1.id + r3.flush() + + r4 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now() + datetime.timedelta(days=1), + "checkout": datetime.datetime.now() + datetime.timedelta(days=2), + "adults": 2, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + r4.reservation_line_ids[0].room_id = self.room3.id + r4.flush() + + r5 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now() + datetime.timedelta(days=2), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + r5.reservation_line_ids[0].room_id = self.room2.id + r5.flush() + + # ACT + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=4), + "adults": 2, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + r_test.flush() + + rooms = 0 + last_room = None + for line in r_test.reservation_line_ids: + if line.room_id != last_room: + last_room = line.room_id + rooms += 1 + + # ASSERT + self.assertEqual( + 3, rooms, "The reservation shouldn't be splitted in more than 3 roomss" + ) + + @freeze_time("2012-01-14") + def test_split_reservation05(self): + """ + # TEST CASE + The preferred room_id is not available + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 |r1/tst| | | | | | + | Double 102 | | | | | | | + | Double 103 | | | | | | | + +------------+------+------+------+----+----+----+ + """ + # ARRANGE + + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + r1.reservation_line_ids[0].room_id = self.room1 + r1.flush() + + # ACT & ASSERT + with self.assertRaises(ValidationError): + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "preferred_room_id": self.room1.id, + "partner_id": self.partner1.id, + } + ) + r_test.flush() + + @freeze_time("2012-01-14") + def test_split_reservation06(self): + """ + # TEST CASE + There's no availability in the preferred_room_id provided + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | r1 |r1/tst| tst | | | | + | Double 102 | | | | | | | + | Double 103 | | | | | | | + +------------+------+------+------+----+----+----+ + """ + # ARRANGE + + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=2), + "adults": 2, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + r1.reservation_line_ids[0].room_id = self.room1 + r1.reservation_line_ids[1].room_id = self.room1 + r1.flush() + + # ACT & ASSERT + with self.assertRaises(ValidationError): + r_test = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now() + datetime.timedelta(days=1), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room1.id, + "partner_id": self.partner1.id, + } + ) + r_test.flush() + + @freeze_time("2012-01-14") + def test_split_reservation07(self): + """ + # TEST CASE + There's no availability + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | r1 | r1 | r1 | | | | + | Double 102 | r2 | r2 | r2 | | | | + | Double 103 | r3 | r3 | r3 | | | | + +------------+------+------+------+----+----+----+ + """ + # ARRANGE + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + r1.reservation_line_ids[0].room_id = self.room1 + r1.reservation_line_ids[1].room_id = self.room1 + r1.reservation_line_ids[2].room_id = self.room1 + r1.flush() + + r2 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + r2.reservation_line_ids[0].room_id = self.room2 + r2.reservation_line_ids[1].room_id = self.room2 + r2.reservation_line_ids[2].room_id = self.room2 + r2.flush() + + r3 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.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.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + r3.reservation_line_ids[0].room_id = self.room3 + r3.reservation_line_ids[1].room_id = self.room3 + r3.reservation_line_ids[2].room_id = self.room3 + r3.flush() + + # ACT & ASSERT + with self.assertRaises(ValidationError): + self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + } + ) + + @freeze_time("2012-01-14") + def test_manage_children_raise(self): + # TEST CASE + """ + Check if the error occurs when trying to put more people than the capacity of the room. + -------------- + Create a reservation with a double room whose capacity is two and try to create + it with two adults and a child occupying the room. + """ + # NO ARRANGE + # ACT & ASSERT + with self.assertRaises( + ValidationError, + msg="The number of people is greater than the capacity of the room", + ): + reservation = self.env["pms.reservation"].create( + { + "adults": 2, + "children_occupying": 1, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "preferred_room_id": self.room1.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + } + ) + reservation.flush() + + @freeze_time("2012-01-14") + def test_to_assign_priority_reservation(self): + """ + To assign reservation must have priority = 1 + ------ + Create a reservation with only room_type (to_assign = True), + regardless of the rest of the fields the priority must be 1 + + NOTE: + WORK FLOW PRIORITY COMPUTE + Check reservation priority + -------- + 1 - TO ASSIGN, ARRIVAL DELAYED, DEPARTURE DELAYED (= 1) + 2 - CANCELLED with pending amount (= 2) + 3 - DONE with pending amount (= 3) + 4 - ONBOARD with pending amount (= days for checkout) + 5 - CONFIRM/DRAFT with arrival in less than 3 days (= 2 * days for checkin) + 6 - ONBOARD all paid (= 3 * days for checkout) + 7 - DONE with days from checkout < 1 (= 6) + 8 - CONFIRM/DRAFT with arrival between 3 and 20 days (= 3 * days for checkin) + 9 - CONFIRM/DRAFT with arrival in more than 20 days (= 4 * days for checkin) + 10 - DONE with days from checkout < 15 (= 5 * days from checkout) + 11 - DONE with days from checkout between 15 and 90 included (= 10 * days from checkout) + 12 - DONE with days from checkout > 90 (= 100 * days from checkout) + """ + # ARRANGE + expected_priority = 1 + + # ACT + res = self.env["pms.reservation"].create( + { + "checkin": fields.date.today() + datetime.timedelta(days=30), + "checkout": fields.date.today() + datetime.timedelta(days=31), + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + computed_priority = res.priority + + # ASSERT + error_msm = ( + ( + "The priority of a reservation to be assigned \ + should be %d and this is %d" + ) + % (expected_priority, computed_priority) + ) + + self.assertEqual( + computed_priority, + expected_priority, + error_msm, + ) + + @freeze_time("2012-01-14") + def test_arrival_delayed_priority_reservation(self): + """ + Arrival delayed reservation must have priority = 1 + ------ + Create a reservation with checkin date yesterday, and without checkin action, + regardless of the rest of the fields the priority must be 1 + """ + # ARRANGE + expected_priority = 1 + res = self.env["pms.reservation"].create( + { + "checkin": fields.date.today() + datetime.timedelta(days=-1), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "preferred_room_id": self.room1.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + + # ACT + res.auto_arrival_delayed() + computed_priority = res.priority + + # ASSERT + error_msm = ( + ( + "The priority of a arrival delayed reservation \ + should be %d and this is %d" + ) + % (expected_priority, computed_priority) + ) + + self.assertEqual( + computed_priority, + expected_priority, + error_msm, + ) + + # @freeze_time("1981-11-10") + # def test_departure_delayed_priority_reservation(self): + # """ + # To departure delayed reservation must have priority = 1 + # ------ + # Create a reservation and make the work flow to onboard state, + # using jump dates, we make the reservation should have left yesterday, + # regardless of the rest of the fields the priority must be 1 + # """ + # # ARRANGE + # expected_priority = 1 + # freezer = freeze_time("1981-11-08") + # freezer.start() + # res = self.env["pms.reservation"].create( + # { + # "checkin": fields.date.today(), + # "checkout": fields.date.today() + datetime.timedelta(days=1), + # "preferred_room_id": self.room2.id, + # "partner_id": self.partner1.id, + # "pms_property_id": self.pms_property1.id, + # } + # ) + # host1 = self.env["res.partner"].create( + # { + # "firstname": "Pepe", + # "lastname": "Paz", + # "email": "pepe@example.com", + # "birthdate_date": "1995-12-10", + # "gender": "male", + # } + # ) + # checkin1 = self.env["pms.checkin.partner"].create( + # { + # "partner_id": host1.id, + # "reservation_id": res.id, + # "document_expedition_date": fields.date.today() + # + datetime.timedelta(days=665), + # } + # ) + # checkin1.action_on_board() + # freezer.stop() + + # # ACT + # res.auto_departure_delayed() + # computed_priority = res.priority + + # # ASSERT + # error_msm = ( + # ( + # "The priority of a departure delayed reservation \ + # should be %d and this is %d" + # ) + # % (expected_priority, computed_priority) + # ) + + # self.assertEqual( + # computed_priority, + # expected_priority, + # error_msm, + # ) + + @freeze_time("2012-01-14") + def _test_cancel_pending_amount_priority_reservation(self): + """ + Cancelled with pending payments reservation must have priority = 2 + ------ + create a reservation and cancel it ensuring that there are + pending payments in it, the priority must be 2 + """ + # ARRANGE + expected_priority = 2 + self.room_type_double.list_price = 25 + res = self.env["pms.reservation"].create( + { + "checkin": fields.date.today() + datetime.timedelta(days=55), + "checkout": fields.date.today() + datetime.timedelta(days=56), + "preferred_room_id": self.room2.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + + # ACT + res.action_cancel() + res.flush() + computed_priority = res.priority + + # ASSERT + error_msm = ( + ( + "The priority of a cancelled reservation with pending amount \ + should be %d and this is %d" + ) + % (expected_priority, computed_priority) + ) + self.assertEqual( + computed_priority, + expected_priority, + error_msm, + ) + + @freeze_time("1981-11-10") + def test_done_with_pending_amountpriority_reservation(self): + """ + Done with pending amount reservation must have priority = 3 + ------ + Create a reservation and make the work flow to onboard - done state, + using jump dates, we make the checkout reservation with pending amount, + regardless of the rest of the fields the priority must be 3 + """ + # ARRANGE + expected_priority = 3 + freezer = freeze_time("1981-10-08") + freezer.start() + res = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "preferred_room_id": self.room2.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + host1 = self.env["res.partner"].create( + { + "firstname": "Pepe", + "lastname": "Paz", + "email": "pepe@example.com", + "birthdate_date": "1995-12-10", + "gender": "male", + } + ) + checkin1 = self.env["pms.checkin.partner"].create( + { + "partner_id": host1.id, + "reservation_id": res.id, + "document_expedition_date": fields.date.today() + + datetime.timedelta(days=665), + } + ) + checkin1.action_on_board() + + freezer.stop() + freezer = freeze_time("1981-10-09") + freezer.start() + + res.action_reservation_checkout() + + # ACT + res.auto_departure_delayed() + computed_priority = res.priority + freezer.stop() + + # ASSERT + error_msm = ( + ( + "The priority of a done reservation with pending amount\ + should be %d and this is %d" + ) + % (expected_priority, computed_priority) + ) + + self.assertEqual( + computed_priority, + expected_priority, + error_msm, + ) + + @freeze_time("1981-11-10") + def test_onboard_with_pending_amount_priority_reservation(self): + """ + Onboard with pending amount reservation must have priority = days for checkout + ------ + Create a reservation with 3 nights and make the work flow to onboard, + using jump dates, we set today in 2 nights before checkout, + regardless of the rest of the fields the priority must be 2 + """ + # ARRANGE + expected_priority = 3 + freezer = freeze_time("1981-10-08") + freezer.start() + res = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=3), + "preferred_room_id": self.room2.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + host1 = self.env["res.partner"].create( + { + "firstname": "Pepe", + "lastname": "Paz", + "email": "pepe@example.com", + "birthdate_date": "1995-12-10", + "gender": "male", + } + ) + checkin1 = self.env["pms.checkin.partner"].create( + { + "partner_id": host1.id, + "reservation_id": res.id, + "document_expedition_date": fields.date.today() + + datetime.timedelta(days=665), + } + ) + + # ACT + checkin1.action_on_board() + computed_priority = res.priority + freezer.stop() + + # ASSERT + error_msm = ( + ( + "The priority of a onboard with payment amount reservation \ + should be %d and this is %d" + ) + % (expected_priority, computed_priority) + ) + + self.assertEqual( + computed_priority, + expected_priority, + error_msm, + ) + + @freeze_time("2012-01-14") + def test_confirm_arriva_lt_3_days_priority_reservation(self): + """ + Confirm reservation with arrival in less than 3 days, priority = 2 * days for checkout + ------ + Create a reservation with checkin date on 2 days + regardless of the rest of the fields the priority must be 2 * 2 = 4 + """ + # ARRANGE + expected_priority = 4 + + # ACT + res = self.env["pms.reservation"].create( + { + "checkin": fields.date.today() + datetime.timedelta(days=2), + "checkout": fields.date.today() + datetime.timedelta(days=5), + "preferred_room_id": self.room2.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + computed_priority = res.priority + + # ASSERT + error_msm = ( + ( + "The priority of a confirm with less than 3 days for arrival \ + reservation should be %d and this is %d" + ) + % (expected_priority, computed_priority) + ) + + self.assertEqual( + computed_priority, + expected_priority, + error_msm, + ) + + @freeze_time("2012-01-14") + def test_onboard_all_pay_priority_reservation(self): + """ + Onboard with all pay reservation must have priority = 3 * days for checkout + ------ + Create a reservation with 3 nights and make the work flow to onboard, + using jump dates, we set today in 2 nights before checkout, + regardless of the rest of the fields the priority must be 3 * 3 = 9 + """ + # ARRANGE + expected_priority = 9 + res = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=3), + "preferred_room_id": self.room2.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + host1 = self.env["res.partner"].create( + { + "firstname": "Pepe", + "lastname": "Paz", + "email": "pepe@example.com", + "birthdate_date": "1995-12-10", + "gender": "male", + } + ) + checkin1 = self.env["pms.checkin.partner"].create( + { + "partner_id": host1.id, + "reservation_id": res.id, + "document_expedition_date": fields.date.today() + + datetime.timedelta(days=665), + } + ) + + # ACT + checkin1.action_on_board() + # REVIEW: set to 0 the price to avoid make the payment + # (config account company issues in test) + res.reservation_line_ids.write({"price": 0}) + computed_priority = res.priority + + # ASSERT + error_msm = ( + ( + "The priority of onboard all pay reservation \ + should be %d and this is %d" + ) + % (expected_priority, computed_priority) + ) + + self.assertEqual( + computed_priority, + expected_priority, + error_msm, + ) + + @freeze_time("1981-11-10") + def test_done_yesterday_all_paid_amountpriority_reservation(self): + """ + Checkout yesterday without pending amount reservation must have priority = 6 + ------ + Create a reservation and make the work flow to onboard - done state, + using jump dates, we make the checkout reservation without pending amount, + and set today 1 day after, + regardless of the rest of the fields the priority must be 6 + """ + # ARRANGE + expected_priority = 6 + freezer = freeze_time("1981-10-08") + freezer.start() + res = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "preferred_room_id": self.room2.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + host1 = self.env["res.partner"].create( + { + "firstname": "Pepe", + "lastname": "Paz", + "email": "pepe@example.com", + "birthdate_date": "1995-12-10", + "gender": "male", + } + ) + checkin1 = self.env["pms.checkin.partner"].create( + { + "partner_id": host1.id, + "reservation_id": res.id, + "document_expedition_date": fields.date.today() + + datetime.timedelta(days=665), + } + ) + checkin1.action_on_board() + + freezer.stop() + freezer = freeze_time("1981-10-09") + freezer.start() + + res.action_reservation_checkout() + # REVIEW: set to 0 the price to avoid make the payment + # (config account company issues in test) + res.reservation_line_ids.write({"price": 0}) + + # ACT + freezer.stop() + freezer = freeze_time("1981-10-10") + freezer.start() + + res.update_daily_priority_reservation() + computed_priority = res.priority + freezer.stop() + + # ASSERT + error_msm = ( + ( + "The priority of a done reservation without pending amount\ + and checkout yesterday should be %d and this is %d" + ) + % (expected_priority, computed_priority) + ) + + self.assertEqual( + computed_priority, + expected_priority, + error_msm, + ) + + @freeze_time("2012-01-14") + def test_confirm_arriva_bt_3_and_20_days_priority_reservation(self): + """ + Confirm reservation with arrival between 3 and 20 days, priority = 3 * days for checkout + ------ + Create a reservation with checkin date on 15 days + regardless of the rest of the fields the priority must be 3 * 15 = 45 + """ + # ARRANGE + expected_priority = 45 + + # ACT + res = self.env["pms.reservation"].create( + { + "checkin": fields.date.today() + datetime.timedelta(days=15), + "checkout": fields.date.today() + datetime.timedelta(days=20), + "preferred_room_id": self.room2.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + computed_priority = res.priority + + # ASSERT + error_msm = ( + ( + "The priority of a confirm with between 3 and 20 days for arrival \ + reservation should be %d and this is %d" + ) + % (expected_priority, computed_priority) + ) + + self.assertEqual( + computed_priority, + expected_priority, + error_msm, + ) + + @freeze_time("2012-01-14") + def test_confirm_arrival_more_than_20_days_priority_reservation(self): + """ + Confirm reservation with arrival more than 20 days, priority = 4 * days for checkout + ------ + Create a reservation with checkin date on 21 days + regardless of the rest of the fields the priority must be 4 * 21 = 84 + """ + # ARRANGE + expected_priority = 84 + + # ACT + res = self.env["pms.reservation"].create( + { + "checkin": fields.date.today() + datetime.timedelta(days=21), + "checkout": fields.date.today() + datetime.timedelta(days=25), + "preferred_room_id": self.room2.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + computed_priority = res.priority + + # ASSERT + error_msm = ( + ( + "The priority of a confirm with more than 20 days for arrival \ + reservation should be %d and this is %d" + ) + % (expected_priority, computed_priority) + ) + + self.assertEqual( + computed_priority, + expected_priority, + error_msm, + ) + + @freeze_time("1981-11-10") + def test_done_checkout_lt_15_days_before_all_paid_priority_reservation(self): + """ + Checkout less than 15 days before without pending amount reservation + must have priority = 5 * days from checkout + ------ + Create a reservation and make the work flow to onboard - done state, + using jump dates, we make the checkout reservation without pending amount, + and set today 6 day after, + regardless of the rest of the fields the priority must be 6 * 5 = 30 + """ + # ARRANGE + expected_priority = 30 + freezer = freeze_time("1981-10-09") + freezer.start() + res = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "preferred_room_id": self.room2.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + host1 = self.env["res.partner"].create( + { + "firstname": "Pepe", + "lastname": "Paz", + "email": "pepe@example.com", + "birthdate_date": "1995-12-10", + "gender": "male", + } + ) + checkin1 = self.env["pms.checkin.partner"].create( + { + "partner_id": host1.id, + "reservation_id": res.id, + "document_expedition_date": fields.date.today() + + datetime.timedelta(days=665), + } + ) + checkin1.action_on_board() + + freezer.stop() + freezer = freeze_time("1981-10-10") + freezer.start() + + res.action_reservation_checkout() + # REVIEW: set to 0 the price to avoid make the payment + # (config account company issues in test) + res.reservation_line_ids.write({"price": 0}) + + # ACT + freezer.stop() + freezer = freeze_time("1981-10-16") + freezer.start() + + res.update_daily_priority_reservation() + computed_priority = res.priority + freezer.stop() + + # ASSERT + error_msm = ( + ( + "The priority of a done reservation without pending amount\ + and checkout less than 15 days before should be %d and this is %d" + ) + % (expected_priority, computed_priority) + ) + + self.assertEqual( + computed_priority, + expected_priority, + error_msm, + ) + + @freeze_time("1981-11-10") + def test_done_checkout_bt_30_and_90_days_before_all_paid_priority_reservation(self): + """ + Checkout between 30 and 90 days before without pending amount reservation + must have priority = 10 * days from checkout + ------ + Create a reservation and make the work flow to onboard - done state, + using jump dates, we make the checkout reservation without pending amount, + and set today 45 day after, + regardless of the rest of the fields the priority must be 10 * 45 = 450 + """ + # ARRANGE + expected_priority = 450 + freezer = freeze_time("1981-10-09") + freezer.start() + res = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "preferred_room_id": self.room2.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + host1 = self.env["res.partner"].create( + { + "firstname": "Pepe", + "lastname": "Paz", + "email": "pepe@example.com", + "birthdate_date": "1995-12-10", + "gender": "male", + } + ) + checkin1 = self.env["pms.checkin.partner"].create( + { + "partner_id": host1.id, + "reservation_id": res.id, + "document_expedition_date": fields.date.today() + + datetime.timedelta(days=665), + } + ) + checkin1.action_on_board() + + freezer.stop() + freezer = freeze_time("1981-10-10") + freezer.start() + + res.action_reservation_checkout() + # REVIEW: set to 0 the price to avoid make the payment + # (config account company issues in test) + res.reservation_line_ids.write({"price": 0}) + + # ACT + freezer.stop() + freezer = freeze_time("1981-11-24") + freezer.start() + + res.update_daily_priority_reservation() + computed_priority = res.priority + freezer.stop() + + # ASSERT + error_msm = ( + ( + "The priority of a done reservation without pending amount\ + and checkout between 30 and 90 days before should be %d and this is %d" + ) + % (expected_priority, computed_priority) + ) + + self.assertEqual( + computed_priority, + expected_priority, + error_msm, + ) + + @freeze_time("1981-11-10") + def test_done_checkout_mt_90_days_before_all_paid_priority_reservation(self): + """ + Checkout more than 90 days before without pending amount reservation + must have priority = 100 * days from checkout + ------ + Create a reservation and make the work flow to onboard - done state, + using jump dates, we make the checkout reservation without pending amount, + and set today 91 day after, + regardless of the rest of the fields the priority must be 100 * 91 = 9100 + """ + # ARRANGE + expected_priority = 9100 + freezer = freeze_time("1981-10-09") + freezer.start() + res = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "preferred_room_id": self.room2.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + host1 = self.env["res.partner"].create( + { + "firstname": "Pepe", + "lastname": "Paz", + "email": "pepe@example.com", + "birthdate_date": "1995-12-10", + "gender": "male", + } + ) + checkin1 = self.env["pms.checkin.partner"].create( + { + "partner_id": host1.id, + "reservation_id": res.id, + "document_expedition_date": fields.date.today() + + datetime.timedelta(days=665), + } + ) + checkin1.action_on_board() + + freezer.stop() + freezer = freeze_time("1981-10-10") + freezer.start() + + res.action_reservation_checkout() + # REVIEW: set to 0 the price to avoid make the payment + # (config account company issues in test) + res.reservation_line_ids.write({"price": 0}) + + # ACT + freezer.stop() + freezer = freeze_time("1982-01-09") + freezer.start() + + res.update_daily_priority_reservation() + computed_priority = res.priority + freezer.stop() + + # ASSERT + error_msm = ( + ( + "The priority of a done reservation without pending amount\ + and checkout more than 90 days before should be %d and this is %d" + ) + % (expected_priority, computed_priority) + ) + + self.assertEqual( + computed_priority, + expected_priority, + error_msm, + ) + + def test_reservation_action_assign(self): + """ + Checks the correct operation of the assign method + --------------- + Create a new reservation with only room_type(autoassign -> to_assign = True), + and the we call to action_assign method to confirm the assignation + """ + res = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + # ACT + res.action_assign() + # ASSERT + self.assertFalse(res.to_assign, "The reservation should be marked as assigned") + + def test_reservation_auto_assign_on_create(self): + """ + When creating a reservation with a specific room, + it is not necessary to mark it as to be assigned + --------------- + Create a new reservation with specific preferred_room_id, + "to_assign" should be set to false automatically + """ + # ARRANGE + + # ACT + res = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "preferred_room_id": self.room1.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + + # ASSERT + self.assertFalse( + res.to_assign, "Reservation with preferred_room_id set to to_assign = True" + ) + + def test_reservation_auto_assign_after_create(self): + """ + When assigning a room manually to a reservation marked "to be assigned", + this field should be automatically unchecked + --------------- + Create a new reservation without preferred_room_id (only room_type), + "to_assign" is True, then set preferred_room_id and "to_assign" should + be set to false automatically + """ + # ARRANGE + # set the priority of the rooms to control the room chosen by auto assign + self.room1.sequence = 1 + self.room2.sequence = 2 + + res = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + + # ACT + # res shoul be room1 in preferred_room_id (minor sequence) + res.preferred_room_id = self.room2.id + + # ASSERT + self.assertFalse( + res.to_assign, + "The reservation should be marked as assigned automatically \ + when assigning the specific room", + ) + + def test_reservation_to_assign_on_create(self): + """ + Check the reservation action assign. + Create a reservation and change the reservation to 'to_assign' = False + through action_assign() method + """ + # ARRANGE + res = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + # ACT + res.action_assign() + # ASSERT + self.assertFalse(res.to_assign, "The reservation should be marked as assigned") + + def test_reservation_action_cancel(self): + """ + Check if the reservation has been cancelled correctly. + ------------- + Create a reservation and change his state to cancelled + through the action_cancel() method. + """ + # ARRANGE + res = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + # ACT + res.action_cancel() + # ASSERT + self.assertEqual(res.state, "cancel", "The reservation should be cancelled") + + @freeze_time("1981-11-01") + def test_reservation_action_checkout(self): + # TEST CASE + """ + Check that when the date of a reservation passes, it goes to the 'done' status. + ------------- + Create a host, a reservation and a check-in partner. Assign the partner and the + reservation to the check-in partner and after one day of the reservation it + must be in the 'done' status + """ + # ARRANGE + host = self.env["res.partner"].create( + { + "name": "Miguel", + "mobile": "654667733", + "email": "miguel@example.com", + "birthdate_date": "1995-12-10", + "gender": "male", + } + ) + self.env["res.partner.id_number"].create( + { + "category_id": self.id_category.id, + "name": "30065089H", + "valid_from": datetime.date.today(), + "partner_id": host.id, + } + ) + r1 = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "room_type_id": self.room_type_double.id, + "partner_id": host.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + r1.flush() + checkin = self.env["pms.checkin.partner"].create( + { + "partner_id": host.id, + "reservation_id": r1.id, + } + ) + checkin.action_on_board() + checkin.flush() + + # ACT + with freeze_time("1981-11-02"): + r1._cache.clear() + r1.action_reservation_checkout() + + # ASSERT + self.assertEqual( + r1.state, "done", "The reservation status should be done after checkout." + ) + + def _test_check_date_order(self): + """ + Check that the date order of a reservation is correct. + --------------- + Create a reservation with today's date and then check that the date order is also today + """ + reservation = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=3), + "partner_id": self.partner1.id, + "room_type_id": self.room_type_double.id, + } + ) + + date_order = reservation.date_order + date_order_expected = datetime.datetime( + date_order.year, + date_order.month, + date_order.day, + date_order.hour, + date_order.minute, + date_order.second, + ) + + reservation.flush() + self.assertEqual( + date_order, + date_order_expected, + "Date Order isn't correct", + ) + + def test_check_checkin_datetime(self): + """ + Check that the checkin datetime of a reservation is correct. + ------------------ + Create a reservation and then check if the checkin datetime + it is correct + """ + reservation = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": fields.date.today() + datetime.timedelta(days=300), + "checkout": fields.date.today() + datetime.timedelta(days=305), + "partner_id": self.partner1.id, + "room_type_id": self.room_type_double.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + r = reservation.checkin + checkin_expected = datetime.datetime(r.year, r.month, r.day, 14, 00) + checkin_expected = self.pms_property1.date_property_timezone(checkin_expected) + + self.assertEqual( + str(reservation.checkin_datetime), + str(checkin_expected), + "Date Order isn't correct", + ) + + def test_check_allowed_room_ids(self): + """ + Check available rooms after creating a reservation. + ----------- + Create an availability rule, create a reservation, + and then check that the allowed_room_ids field filtered by room + type of the reservation and the room_type_id.room_ids field of the + availability rule match. + """ + availability_rule = self.env["pms.availability.plan.rule"].create( + { + "pms_property_id": self.pms_property1.id, + "room_type_id": self.room_type_double.id, + "availability_plan_id": self.room_type_availability.id, + "date": fields.date.today() + datetime.timedelta(days=153), + } + ) + reservation = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": fields.date.today() + datetime.timedelta(days=150), + "checkout": fields.date.today() + datetime.timedelta(days=152), + "partner_id": self.partner1.id, + "room_type_id": self.room_type_double.id, + "pricelist_id": self.pricelist1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + self.assertEqual( + reservation.allowed_room_ids.filtered( + lambda r: r.room_type_id.id == availability_rule.room_type_id.id + ).ids, + availability_rule.room_type_id.room_ids.ids, + "Rooms allowed don't match", + ) + + def test_partner_is_agency(self): + """ + Check that a reservation created with an agency and without a partner + assigns that agency as a partner. + ------------- + Create an agency and then create a reservation to which that agency + assigns but does not associate any partner. + Then check that the partner of that reservation is the same as the agency + """ + sale_channel1 = self.env["pms.sale.channel"].create( + {"name": "Test Indirect", "channel_type": "indirect"} + ) + agency = self.env["res.partner"].create( + { + "name": "partner1", + "is_agency": True, + "sale_channel_id": sale_channel1.id, + "invoice_to_agency": "always", + } + ) + + reservation = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": fields.date.today() + datetime.timedelta(days=150), + "checkout": fields.date.today() + datetime.timedelta(days=152), + "agency_id": agency.id, + "room_type_id": self.room_type_double.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + + reservation.flush() + + self.assertEqual( + reservation.partner_id.id, + agency.id, + "Partner_id doesn't match with agency_id", + ) + + def test_agency_pricelist(self): + """ + Check that a pricelist of a reservation created with an + agency and without a partner and the pricelist of that + agency are the same. + ------------- + Create an agency with field apply_pricelist is True and + then create a reservation to which that agency + assigns but does not associate any partner. + Then check that the pricelist of that reservation is the same as the agency + """ + sale_channel1 = self.env["pms.sale.channel"].create( + { + "name": "Test Indirect", + "channel_type": "indirect", + "product_pricelist_ids": [(6, 0, [self.pricelist1.id])], + } + ) + agency = self.env["res.partner"].create( + { + "name": "partner1", + "is_agency": True, + "sale_channel_id": sale_channel1.id, + "apply_pricelist": True, + } + ) + + reservation = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": fields.date.today() + datetime.timedelta(days=150), + "checkout": fields.date.today() + datetime.timedelta(days=152), + "agency_id": agency.id, + "room_type_id": self.room_type_double.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + self.assertEqual( + reservation.pricelist_id.id, + reservation.agency_id.property_product_pricelist.id, + "Rervation pricelist doesn't match with Agency pricelist", + ) + + def test_compute_access_url(self): + """ + Check that the access_url field of the reservation is created with a correct value. + ------------- + Create a reservation and then check that the access_url field has the value + my/reservation/(reservation.id) + """ + reservation = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": fields.date.today() + datetime.timedelta(days=150), + "checkout": fields.date.today() + datetime.timedelta(days=152), + "partner_id": self.partner1.id, + "room_type_id": self.room_type_double.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + + url = "/my/reservations/%s" % reservation.id + self.assertEqual(reservation.access_url, url, "Reservation url isn't correct") + + @freeze_time("2012-01-14") + def test_compute_ready_for_checkin(self): + """ + Check that the ready_for_checkin field is True when the reservation + checkin day is today. + --------------- + Create two hosts, create a reservation with a checkin date today, + and associate two checkin partners with that reservation and with + each of the hosts. + Then check that the ready_for_checkin field of the reservation is True + """ + self.host1 = self.env["res.partner"].create( + { + "name": "Miguel", + "mobile": "654667733", + "email": "miguel@example.com", + "birthdate_date": "1995-12-10", + "gender": "male", + } + ) + self.env["res.partner.id_number"].create( + { + "category_id": self.id_category.id, + "name": "30065000H", + "valid_from": datetime.date.today(), + "partner_id": self.host1.id, + } + ) + self.host2 = self.env["res.partner"].create( + { + "name": "Brais", + "mobile": "654437733", + "email": "brais@example.com", + "birthdate_date": "1995-12-10", + "gender": "male", + } + ) + self.env["res.partner.id_number"].create( + { + "category_id": self.id_category.id, + "name": "30065089H", + "valid_from": datetime.date.today(), + "partner_id": self.host2.id, + } + ) + self.reservation = self.env["pms.reservation"].create( + { + "checkin": "2012-01-14", + "checkout": "2012-01-17", + "partner_id": self.host1.id, + "allowed_checkin": True, + "pms_property_id": self.pms_property1.id, + "adults": 3, + "room_type_id": self.room_type_triple.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + self.checkin1 = self.env["pms.checkin.partner"].create( + { + "partner_id": self.host1.id, + "reservation_id": self.reservation.id, + } + ) + self.checkin2 = self.env["pms.checkin.partner"].create( + { + "partner_id": self.host2.id, + "reservation_id": self.reservation.id, + } + ) + + self.reservation.checkin_partner_ids = [ + (6, 0, [self.checkin1.id, self.checkin2.id]) + ] + self.assertTrue( + self.reservation.ready_for_checkin, + "Reservation should is ready for checkin", + ) + + def test_check_checkout_less_checkin(self): + """ + Check that a reservation cannot be created with the + checkin date greater than the checkout date + --------------- + Create a reservation with the checkin date 3 days + after the checkout date, this should throw an error. + """ + self.host1 = self.env["res.partner"].create( + { + "name": "Host1", + } + ) + with self.assertRaises(UserError): + self.env["pms.reservation"].create( + { + "checkin": fields.date.today() + datetime.timedelta(days=3), + "checkout": fields.date.today(), + "pms_property_id": self.pms_property1.id, + "partner_id": self.host1.id, + "room_type_id": self.room_type_double.id, + } + ) + + @freeze_time("2012-01-14") + def test_check_more_adults_than_beds(self): + """ + Check that a reservation cannot be created when the field + adults is greater than the capacity of the room. + ------------- + Try to create a reservation with a double room and the + field 'adults'=4, this should throw a mistake because the + room capacity is lesser than the number of adults. + """ + self.host1 = self.env["res.partner"].create( + { + "name": "Host1", + } + ) + with self.assertRaises(ValidationError): + reservation = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=3), + "pms_property_id": self.pms_property1.id, + "partner_id": self.host1.id, + "preferred_room_id": self.room1.id, + "adults": 4, + } + ) + reservation.flush() + + @freeze_time("2012-01-14") + def test_check_format_arrival_hour(self): + """ + Check that the format of the arrival_hour field is correct(HH:mm) + ------------- + Create a reservation with the wrong arrival hour date + format (HH:mm:ss), this should throw an error. + """ + self.host1 = self.env["res.partner"].create( + { + "name": "Host1", + } + ) + with self.assertRaises(ValidationError): + self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=3), + "pms_property_id": self.pms_property1.id, + "partner_id": self.host1.id, + "arrival_hour": "14:00:00", + } + ) + + @freeze_time("2012-01-14") + def test_check_format_departure_hour(self): + """ + Check that the format of the departure_hour field is correct(HH:mm) + ------------- + Create a reservation with the wrong departure hour date + format (HH:mm:ss), this should throw an error. + """ + self.host1 = self.env["res.partner"].create( + { + "name": "Host1", + } + ) + with self.assertRaises(ValidationError): + self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=3), + "pms_property_id": self.pms_property1.id, + "partner_id": self.host1.id, + "departure_hour": "14:00:00", + } + ) + + @freeze_time("2012-01-14") + def test_check_property_integrity_room(self): + """ + Check that a reservation cannot be created with a room + of a different property. + ------------ + Try to create a reservation for property2 with a + preferred_room that belongs to property1, this + should throw an error . + """ + self.property2 = self.env["pms.property"].create( + { + "name": "MY PMS TEST", + "company_id": self.company1.id, + "default_pricelist_id": self.pricelist1.id, + } + ) + self.host1 = self.env["res.partner"].create( + { + "name": "Host1", + } + ) + self.room_type_double.pms_property_ids = [ + (6, 0, [self.pms_property1.id, self.property2.id]) + ] + with self.assertRaises(ValidationError): + self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=3), + "pms_property_id": self.property2.id, + "partner_id": self.host1.id, + "room_type_id": self.room_type_double.id, + "preferred_room_id": self.room1.id, + } + ) + + @freeze_time("2012-01-14") + def test_shared_folio_true(self): + """ + Check that the shared_folio field of a reservation whose + folio has other reservations is True. + --------- + Create a reservation and then create another reservation with + its folio_id = folio_id of the previous reservation. This + should set shared_folio to True + """ + self.host1 = self.env["res.partner"].create( + { + "name": "Host1", + } + ) + self.reservation = self.env["pms.reservation"].create( + { + "checkin": fields.date.today() + datetime.timedelta(days=60), + "checkout": fields.date.today() + datetime.timedelta(days=65), + "pms_property_id": self.pms_property1.id, + "partner_id": self.host1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + self.reservation2 = self.env["pms.reservation"].create( + { + "checkin": fields.date.today() + datetime.timedelta(days=60), + "checkout": fields.date.today() + datetime.timedelta(days=64), + "pms_property_id": self.pms_property1.id, + "partner_id": self.host1.id, + "folio_id": self.reservation.folio_id.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + self.assertTrue( + self.reservation.shared_folio, + "Folio.reservations > 1, so reservation.shared_folio must be True", + ) + + @freeze_time("2012-01-14") + def test_shared_folio_false(self): + """ + Check that the shared_folio field for a reservation whose folio has no + other reservations is False. + """ + self.host1 = self.env["res.partner"].create( + { + "name": "Host1", + } + ) + self.reservation = self.env["pms.reservation"].create( + { + "checkin": fields.date.today() + datetime.timedelta(days=60), + "checkout": fields.date.today() + datetime.timedelta(days=65), + "pms_property_id": self.pms_property1.id, + "partner_id": self.host1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + self.assertFalse( + self.reservation.shared_folio, + "Folio.reservations = 1, so reservation.shared_folio must be False", + ) + + @freeze_time("2012-01-14") + def test_reservation_action_cancel_fail(self): + """ + Check that a reservation cannot be in the cancel state if + the cancellation is not allowed. + --------- + Create a reservation, put its state = "canceled" and then try to + pass its state to cancel using the action_cancel () method. This + should throw an error because a reservation with state cancel cannot + be canceled again. + """ + self.host1 = self.env["res.partner"].create( + { + "name": "Host1", + } + ) + reservation = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "room_type_id": self.room_type_double.id, + "partner_id": self.host1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + + with self.assertRaises(ValidationError): + reservation.state = "cancel" + + @freeze_time("2012-01-14") + def test_cancelation_reason_noshow(self): + """ + Check that if a reservation has already passed and there is no check-in, + the reason for cancellation must be 'no-show' + ------ + Create a cancellation rule that is assigned to a pricelist. Then create + a reservation with a past date and the action_cancel method is launched. + The canceled_reason field is verified to be is equal to "no_show" + """ + Pricelist = self.env["product.pricelist"] + self.cancelation_rule = self.env["pms.cancelation.rule"].create( + { + "name": "Cancelation Rule Test", + "pms_property_ids": [self.pms_property1.id], + "penalty_noshow": 50, + } + ) + + self.pricelist = Pricelist.create( + { + "name": "Pricelist Test", + "pms_property_ids": [self.pms_property1.id], + "cancelation_rule_id": self.cancelation_rule.id, + "availability_plan_id": self.availability_plan1.id, + "is_pms_available": True, + } + ) + self.host1 = self.env["res.partner"].create( + { + "name": "Host1", + } + ) + + reservation = self.env["pms.reservation"].create( + { + "checkin": fields.date.today() + datetime.timedelta(days=-5), + "checkout": fields.date.today() + datetime.timedelta(days=3), + "room_type_id": self.room_type_double.id, + "partner_id": self.host1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + reservation.action_cancel() + reservation.flush() + self.assertEqual( + reservation.cancelled_reason, + "noshow", + "If reservation has already passed and no checkin," + "cancelled_reason must be 'noshow'", + ) + + @freeze_time("2012-01-14") + def test_cancelation_reason_intime(self): + """ + Check that if a reservation is canceled on time according + to the cancellation rules the canceled_reason field must be "intime" + ------ + Create a cancellation rule assigned to a price list with + the field days_intime = 3. Then create a reservation with + a checkin date within 5 days and launch the action_cancel method. + canceled_reason field must be "intime" + """ + Pricelist = self.env["product.pricelist"] + self.cancelation_rule = self.env["pms.cancelation.rule"].create( + { + "name": "Cancelation Rule Test", + "pms_property_ids": [self.pms_property1.id], + "days_intime": 3, + } + ) + + self.pricelist = Pricelist.create( + { + "name": "Pricelist Test", + "pms_property_ids": [self.pms_property1.id], + "cancelation_rule_id": self.cancelation_rule.id, + "availability_plan_id": self.availability_plan1.id, + "is_pms_available": True, + } + ) + self.host1 = self.env["res.partner"].create( + { + "name": "Host1", + } + ) + + reservation = self.env["pms.reservation"].create( + { + "checkin": fields.date.today() + datetime.timedelta(days=5), + "checkout": fields.date.today() + datetime.timedelta(days=8), + "room_type_id": self.room_type_double.id, + "partner_id": self.host1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + + reservation.action_cancel() + reservation.flush() + + self.assertEqual( + reservation.cancelled_reason, "intime", "Cancelled reason must be 'intime'" + ) + + @freeze_time("2012-01-14") + def test_cancelation_reason_late(self): + """ + Check that if a reservation is canceled outside the cancellation + period, the canceled_reason field of the reservation must be "late" . + --------- + Create a cancellation rule with the field days_intime = 3. + A reservation is created with a checkin date for tomorrow and the + action_cancel() method is launched. As the reservation was canceled + after the deadline, the canceled_reason field must be late + """ + Pricelist = self.env["product.pricelist"] + self.cancelation_rule = self.env["pms.cancelation.rule"].create( + { + "name": "Cancelation Rule Test", + "pms_property_ids": [self.pms_property1.id], + "days_intime": 3, + } + ) + + self.pricelist = Pricelist.create( + { + "name": "Pricelist Test", + "pms_property_ids": [self.pms_property1.id], + "cancelation_rule_id": self.cancelation_rule.id, + "availability_plan_id": self.availability_plan1.id, + "is_pms_available": True, + } + ) + self.host1 = self.env["res.partner"].create( + { + "name": "Host1", + } + ) + + reservation = self.env["pms.reservation"].create( + { + "checkin": fields.date.today() + datetime.timedelta(days=1), + "checkout": fields.date.today() + datetime.timedelta(days=4), + "room_type_id": self.room_type_double.id, + "partner_id": self.host1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + reservation.action_cancel() + reservation.flush() + self.assertEqual(reservation.cancelled_reason, "late", "-----------") + + @freeze_time("2012-01-14") + def test_compute_checkin_partner_count(self): + """ + Check that the number of guests of a reservation is equal + to the checkin_partner_count field of that same reservation. + ------------- + Create 2 checkin partners. Create a reservation with those + two checkin partners. The checkin_partner_count field must + be equal to the number of checkin partners in the reservation. + """ + self.host1 = self.env["res.partner"].create( + { + "name": "Miguel", + "mobile": "654667733", + "email": "miguel@example.com", + } + ) + self.host2 = self.env["res.partner"].create( + { + "name": "Brais", + "mobile": "654437733", + "email": "brais@example.com", + } + ) + self.reservation = self.env["pms.reservation"].create( + { + "checkin": "2012-01-14", + "checkout": "2012-01-17", + "partner_id": self.host1.id, + "pms_property_id": self.pms_property1.id, + "adults": 3, + "room_type_id": self.room_type_triple.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + self.checkin1 = self.env["pms.checkin.partner"].create( + { + "partner_id": self.host1.id, + "reservation_id": self.reservation.id, + } + ) + self.checkin2 = self.env["pms.checkin.partner"].create( + { + "partner_id": self.host2.id, + "reservation_id": self.reservation.id, + } + ) + + self.reservation.checkin_partner_ids = [ + (6, 0, [self.checkin1.id, self.checkin2.id]) + ] + + self.assertEqual( + self.reservation.checkin_partner_count, + len(self.reservation.checkin_partner_ids), + "Checkin_partner_count must be match with number of checkin_partner_ids", + ) + + @freeze_time("2012-01-14") + def test_compute_checkin_partner_pending_count(self): + """ + Check that the checkin_partner_count field gives + the expected result. + -------------- + Create a reservation with 3 adults and associate 2 + checkin partners with that reservation. The + checkin_partner_pending_count field must be the + same as the difference between the adults in the + reservation and the number of checkin_partner_ids in + the reservation + """ + self.host1 = self.env["res.partner"].create( + { + "name": "Miguel", + "mobile": "654667733", + "email": "miguel@example.com", + } + ) + self.host2 = self.env["res.partner"].create( + { + "name": "Brais", + "mobile": "654437733", + "email": "brais@example.com", + } + ) + self.reservation = self.env["pms.reservation"].create( + { + "checkin": "2012-01-14", + "checkout": "2012-01-17", + "partner_id": self.host1.id, + "pms_property_id": self.pms_property1.id, + "room_type_id": self.room_type_triple.id, + "adults": 3, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + self.checkin1 = self.env["pms.checkin.partner"].create( + { + "partner_id": self.host1.id, + "reservation_id": self.reservation.id, + } + ) + self.checkin2 = self.env["pms.checkin.partner"].create( + { + "partner_id": self.host2.id, + "reservation_id": self.reservation.id, + } + ) + + self.reservation.checkin_partner_ids = [ + (6, 0, [self.checkin1.id, self.checkin2.id]) + ] + + count_expected = self.reservation.adults - len( + self.reservation.checkin_partner_ids + ) + self.assertEqual( + self.reservation.checkin_partner_pending_count, + count_expected, + "Checkin_partner_pending_count isn't correct", + ) + + @freeze_time("2012-01-14") + def test_reservation_action_checkout_fail(self): + """ + Check that a reservation cannot be checkout because + the checkout is not allowed. + --------------- + Create a reservation and try to launch the action_reservation_checkout + method, but this should throw an error, because for the + checkout to be allowed, the reservation must be in "onboard" + or "departure_delayed" state + """ + host = self.env["res.partner"].create( + { + "name": "Miguel", + "mobile": "654667733", + "email": "miguel@example.com", + } + ) + reservation = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "partner_id": host.id, + "allowed_checkout": True, + "pms_property_id": self.pms_property1.id, + "room_type_id": self.room_type_double.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + + with self.assertRaises(UserError): + reservation.action_reservation_checkout() + + @freeze_time("2012-01-14") + def test_partner_name_folio(self): + """ + Check that a reservation without a partner_name + is associated with the partner_name of its folio + ---------- + Create a folio with a partner_name. Then create a + reservation with folio_id = folio.id and without + partner_name. The partner name of the reservation + and the folio must be the same + """ + + # ARRANGE + self.folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_name": "Solón", + } + ) + + self.reservation = self.env["pms.reservation"].create( + { + "checkin": "2012-01-14", + "checkout": "2012-01-17", + "pms_property_id": self.pms_property1.id, + "folio_id": self.folio1.id, + "room_type_id": self.room_type_double.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + # ACT AND ASSERT + self.assertEqual( + self.folio1.partner_name, + self.reservation.partner_name, + "The folio partner name and the reservation partner name doesn't correspond", + ) + + @freeze_time("2012-01-14") + def test_partner_is_agency_not_invoice_to_agency(self): + """ + Check that a reservation without partner_name but with + an agency whose field invoice_to_agency = False will + be set as partner_name "Reservation_from (agency name)" + ------------- + Create an agency with invoice_to_agency = False + and then create a reservation to which that agency + assigns but does not associate any partner. + Then check that the partner_name of that reservation is "Reservation from (agency name)" + """ + sale_channel1 = self.env["pms.sale.channel"].create( + {"name": "Test Indirect", "channel_type": "indirect"} + ) + agency = self.env["res.partner"].create( + { + "name": "partner1", + "is_agency": True, + "sale_channel_id": sale_channel1.id, + } + ) + + reservation = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": fields.date.today() + datetime.timedelta(days=150), + "checkout": fields.date.today() + datetime.timedelta(days=152), + "agency_id": agency.id, + "room_type_id": self.room_type_double.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + + reservation.flush() + + self.assertEqual( + reservation.partner_name, + "Reservation from " + agency.name, + "Partner name doesn't match with to the expected", + ) + + @freeze_time("2010-11-10") + def test_cancel_discount_board_service(self): + """ + When a reservation is cancelled, service discount in case of board_services + must be equal to the discounts of each reservation_line. + + """ + + # ARRANGE + self.cancelation_rule = self.env["pms.cancelation.rule"].create( + { + "name": "Cancelation Rule Test", + "penalty_noshow": 50, + "apply_on_noshow": "all", + } + ) + + self.pricelist1.cancelation_rule_id = self.cancelation_rule.id + + self.product = self.env["product.product"].create( + { + "name": "Product test", + "per_day": True, + "consumed_on": "after", + } + ) + self.board_service = self.env["pms.service"].create( + { + "is_board_service": True, + "product_id": self.product.id, + } + ) + + self.room_type_double.list_price = 25 + reservation = self.env["pms.reservation"].create( + { + "checkin": fields.date.today() + datetime.timedelta(days=-3), + "checkout": fields.date.today() + datetime.timedelta(days=3), + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "service_ids": [self.board_service.id], + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + # ACTION + reservation.action_cancel() + reservation.flush() + # ASSERT + self.assertEqual( + reservation.reservation_line_ids.mapped("cancel_discount")[0], + reservation.service_ids.service_line_ids.mapped("cancel_discount")[0], + "Cancel discount of reservation service lines must be the same " + "that reservation board services", + ) + + @freeze_time("2011-10-10") + def _test_cancel_discount_reservation_line(self): + """ + When a reservation is cancelled, cancellation discount is given + by the cancellation rule associated with the reservation pricelist. + Each reservation_line calculates depending on the cancellation + reason which is the correspondig discount. In this case the + cancellation reason is'noshow' and the rule specifies that 50% must + be reducted every day, that is, on each of reseravtion_lines + """ + # ARRANGE + self.cancelation_rule = self.env["pms.cancelation.rule"].create( + { + "name": "Cancelation Rule Test", + "penalty_noshow": 50, + "apply_on_noshow": "all", + } + ) + + self.pricelist1.cancelation_rule_id = self.cancelation_rule.id + + self.room_type_double.list_price = 50 + reservation = self.env["pms.reservation"].create( + { + "checkin": fields.date.today() + datetime.timedelta(days=-3), + "checkout": fields.date.today() + datetime.timedelta(days=3), + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + + # ACTION + reservation.action_cancel() + reservation.flush() + + # ASSERT + self.assertEqual( + set(reservation.reservation_line_ids.mapped("cancel_discount")), + {self.cancelation_rule.penalty_noshow}, + "Cancel discount of reservation_lines must be equal than cancellation rule penalty", + ) + + @freeze_time("2011-11-11") + def test_cancel_discount_service(self): + """ + When a reservation is cancelled, service discount in + services that are not board_services ALWAYS have to be 100%, + refardless of the cancellation rule associated with the pricelist + """ + # ARRANGE + self.cancelation_rule = self.env["pms.cancelation.rule"].create( + { + "name": "Cancelation Rule Test", + "penalty_noshow": 50, + "apply_on_noshow": "all", + } + ) + + self.pricelist1.cancelation_rule_id = self.cancelation_rule.id + + self.product = self.env["product.product"].create( + { + "name": "Product test", + "per_day": True, + "consumed_on": "after", + "is_extra_bed": True, + } + ) + self.service = self.env["pms.service"].create( + { + "is_board_service": False, + "product_id": self.product.id, + } + ) + + reservation = self.env["pms.reservation"].create( + { + "checkin": fields.date.today() + datetime.timedelta(days=-3), + "checkout": fields.date.today() + datetime.timedelta(days=3), + "preferred_room_id": self.room1.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "service_ids": [self.service.id], + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + + expected_cancel_discount = 100 + + # ACTION + reservation.action_cancel() + reservation.flush() + # ASSERT + self.assertEqual( + expected_cancel_discount, + reservation.service_ids.service_line_ids.mapped("cancel_discount")[0], + "Cancel discount of services must be 100%", + ) + + @freeze_time("2011-06-06") + def test_discount_in_service(self): + """ + Discount in pms.service is calculated from the + discounts that each if its service lines has, + in this case when reservation is cancelled a + 50% cancellation discount is applied and + there aren't other different discounts + """ + + # ARRANGE + self.cancelation_rule = self.env["pms.cancelation.rule"].create( + { + "name": "Cancelation Rule Test", + "penalty_noshow": 50, + "apply_on_noshow": "all", + } + ) + + self.pricelist1.cancelation_rule_id = self.cancelation_rule.id + + self.product = self.env["product.product"].create( + { + "name": "Product test", + "per_day": True, + "consumed_on": "after", + } + ) + self.board_service = self.env["pms.service"].create( + { + "is_board_service": True, + "product_id": self.product.id, + } + ) + + self.room_type_double.list_price = 25 + reservation = self.env["pms.reservation"].create( + { + "checkin": fields.date.today() + datetime.timedelta(days=-3), + "checkout": fields.date.today() + datetime.timedelta(days=3), + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "service_ids": [self.board_service.id], + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + + # ACTION + reservation.action_cancel() + reservation.flush() + + expected_discount = sum( + sl.price_day_total * sl.cancel_discount / 100 + for sl in self.board_service.service_line_ids + ) + # ASSERT + self.assertEqual( + expected_discount, + self.board_service.discount, + "Service discount must be the sum of its services_lines discount", + ) + + @freeze_time("2011-11-11") + def test_services_discount_in_reservation(self): + """ + Services discount in reservation is equal to the sum of the discounts of all + its services, whether they are board_services or not + """ + # ARRANGE + self.cancelation_rule = self.env["pms.cancelation.rule"].create( + { + "name": "Cancelation Rule Test", + "penalty_noshow": 50, + "apply_on_noshow": "all", + } + ) + + self.pricelist1.cancelation_rule_id = self.cancelation_rule.id + + self.product1 = self.env["product.product"].create( + { + "name": "Product test1", + "per_day": True, + "consumed_on": "after", + "is_extra_bed": True, + } + ) + self.service = self.env["pms.service"].create( + { + "is_board_service": False, + "product_id": self.product1.id, + } + ) + self.service.flush() + self.product2 = self.env["product.product"].create( + { + "name": "Product test 2", + "per_person": True, + "consumed_on": "after", + } + ) + self.board_service = self.env["pms.service"].create( + { + "is_board_service": True, + "product_id": self.product2.id, + } + ) + + self.room_type_double.list_price = 25 + checkin = fields.date.today() + datetime.timedelta(days=-3) + checkout = fields.date.today() + datetime.timedelta(days=3) + reservation = self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "service_ids": [self.service.id, self.board_service.id], + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + + # ACTION + reservation.action_cancel() + reservation.flush() + + expected_discount = sum(s.discount for s in reservation.service_ids) + + # ASSERT + self.assertEqual( + expected_discount, + reservation.services_discount, + "Services discount isn't the expected", + ) + + @freeze_time("2011-12-12") + def _test_price_services_in_reservation(self): + """ + Service price total in a reservation corresponds to the sum of prices + of all its services less the total discount of that services + """ + # ARRANGE + self.cancelation_rule = self.env["pms.cancelation.rule"].create( + { + "name": "Cancelation Rule Test", + "penalty_noshow": 50, + "apply_on_noshow": "all", + } + ) + + self.pricelist1.cancelation_rule_id = self.cancelation_rule.id + + self.product1 = self.env["product.product"].create( + { + "name": "Product test1", + "per_day": True, + "consumed_on": "after", + "is_extra_bed": True, + } + ) + self.service = self.env["pms.service"].create( + { + "is_board_service": False, + "product_id": self.product1.id, + } + ) + self.service.flush() + self.product2 = self.env["product.product"].create( + { + "name": "Product test 2", + "per_person": True, + "consumed_on": "after", + } + ) + self.board_service = self.env["pms.service"].create( + { + "is_board_service": True, + "product_id": self.product2.id, + } + ) + + self.room_type_double.list_price = 25 + checkin = fields.date.today() + datetime.timedelta(days=-3) + checkout = fields.date.today() + datetime.timedelta(days=3) + reservation = self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "service_ids": [self.service.id, self.board_service.id], + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + + # ACTION + reservation.action_cancel() + reservation.flush() + expected_price = round( + ( + self.service.price_total + + self.board_service.price_total * reservation.adults + ) + - reservation.services_discount, + 2, + ) + # ASSERT + self.assertEqual( + expected_price, + reservation.price_services, + "Services price isn't the expected", + ) + + @freeze_time("2011-08-08") + def test_room_discount_in_reservation(self): + """ + Discount in pms.reservation is calculated from the + discounts that each if its reservation lines has, + in this case when reservation is cancelled a 50% + cancellation discount is applied and + there aren't other different discounts + """ + # ARRANGE + self.cancelation_rule = self.env["pms.cancelation.rule"].create( + { + "name": "Cancelation Rule Test", + "penalty_noshow": 50, + "apply_on_noshow": "all", + } + ) + + self.pricelist1.cancelation_rule_id = self.cancelation_rule.id + + self.room_type_double.list_price = 30 + checkin = fields.date.today() + datetime.timedelta(days=-3) + checkout = fields.date.today() + datetime.timedelta(days=3) + reservation = self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + + # ACTION + reservation.action_cancel() + reservation.flush() + + expected_discount = sum( + rl.price * rl.cancel_discount / 100 + for rl in reservation.reservation_line_ids + ) + + # ASSERT + self.assertEqual( + expected_discount, + reservation.discount, + "Room discount isn't the expected", + ) + + @freeze_time("2012-01-14") + def test_default_normal_reservation_type(self): + """ + Check that the default reservation type is "normal". + ----------- + A reservation is created without defining the reservation_type + field and it is checked that it is 'normal' + """ + # ARRANGE + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=3) + self.partner1 = self.env["res.partner"].create({"name": "Ana"}) + folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_id": self.partner1.id, + } + ) + # ACT + self.room_type_double.write({"list_price": 30}) + reservation = self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "folio_id": folio1.id, + } + ) + # ASSERT + self.assertEqual( + reservation.reservation_type, + "normal", + "The default reservation type should be 'normal'", + ) + + @freeze_time("2012-01-14") + def test_price_normal_reservation(self): + """ + Check the price of a normal type reservation. + ----------- + A reservation is created for a room with price 30. + Then it is verified that the total price of the + reservation is equal to the price of the room multiplied + by the number of days of the reservation. + """ + # ARRANGE + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=3) + # ACT + self.room_type_double.write({"list_price": 30}) + reservation = self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + diff_days = (checkout - checkin).days + expected_price = self.room_type_double.list_price * diff_days + # ASSERT + self.assertEqual( + reservation.price_total, + expected_price, + "The expected price of the reservation is not correct", + ) + + @freeze_time("2012-01-14") + def test_price_staff_reservation(self): + """ + Check that the price of a staff type reservation + is not calculated. + ------------- + A reservation is created with the reservation_type field as 'staff'. + Then it is verified that the price of the reservation is equal to 0. + """ + # ARRANGE + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=3) + # ACT + self.room_type_double.write({"list_price": 30}) + reservation = self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "reservation_type": "staff", + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + # ASSERT + self.assertEqual( + reservation.price_total, + 0.0, + "The expected price of the reservation is not correct", + ) + + @freeze_time("2012-01-14") + def test_price_out_of_service_reservation(self): + """ + Check that the price of a out type reservation + is not calculated. + ------------- + A reservation is created with the reservation_type field as 'out'. + Then it is verified that the price of the reservation is equal to 0. + """ + # ARRANGE + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=3) + closure_reason = self.env["room.closure.reason"].create( + { + "name": "test closure reason", + "description": "test clopsure reason description", + } + ) + # ACT + self.room_type_double.write({"list_price": 30}) + reservation = self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "reservation_type": "out", + "closure_reason_id": closure_reason.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + # ASSERT + self.assertEqual( + reservation.price_total, + 0.0, + "The expected price of the reservation is not correct", + ) + + # @freeze_time("2012-01-14") + # def test_no_pricelist_staff_reservation(self): + # """ + # Check that in a staff type reservation the pricelist is False. + # ------------- + # A reservation is created with the reservation_type field as 'staff'. + # Then it is verified that the pricelist of the reservation is False. + # """ + # # ARRANGE + # checkin = fields.date.today() + # checkout = fields.date.today() + datetime.timedelta(days=3) + # # ACT + # reservation = self.env["pms.reservation"].create( + # { + # "checkin": checkin, + # "checkout": checkout, + # "room_type_id": self.room_type_double.id, + # "partner_id": self.partner1.id, + # "pms_property_id": self.pms_property1.id, + # "reservation_type": "staff", + # "sale_channel_origin_id": self.sale_channel_direct.id, + # } + # ) + # + # self.assertFalse( + # reservation.pricelist_id, + # "The pricelist of a staff reservation should be False", + # ) + + @freeze_time("2012-01-14") + def test_no_pricelist_out_reservation(self): + """ + Check that in a out type reservation the pricelist is False. + ------------- + A reservation is created with the reservation_type field as 'out'. + Then it is verified that the pricelist of the reservation is False. + """ + # ARRANGE + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=3) + closure_reason = self.env["room.closure.reason"].create( + { + "name": "test closure reason", + "description": "test clopsure reason description", + } + ) + # ACT + self.room_type_double.write({"list_price": 30}) + reservation = self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "reservation_type": "out", + "closure_reason_id": closure_reason.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + + self.assertFalse( + reservation.pricelist_id, + "The pricelist of a out of service reservation should be False", + ) + + @freeze_time("2012-01-14") + def test_reservation_type_by_folio(self): + """ + Check that the reservation type field in a reservation is the + same as the reservation type on the folio that contains + that reservation. + -------------------------- + A folio is created with the field reservation_type as 'staff'. + A reservation is created to which the + value of the folio_id is the id of the previously created + folio. Then it is verified that the value of the reservation_type + field of the reservation is the same that reservation_type in the folio: + 'staff'. + """ + # ARRANGE AND ACT + self.partner1 = self.env["res.partner"].create({"name": "Ana"}) + folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_id": self.partner1.id, + "reservation_type": "staff", + } + ) + + reservation1 = self.env["pms.reservation"].create( + { + "room_type_id": self.room_type_double.id, + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "folio_id": folio1.id, + } + ) + + # ASSERT + self.assertEqual( + reservation1.reservation_type, + "staff", + "The reservation type of the folio should be 'staff'", + ) + + @freeze_time("2012-01-14") + def test_no_partner_id_out_reservation(self): + """ + Check that a reservation of type out of service does not + have a partner_id. + ------------------ + A reservation is created without a partner_id and with the + value of the field reservation_type as '' out. Then it is + checked that the partner_id field of the reservation is False + """ + # ARRANGE + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=3) + closure_reason = self.env["room.closure.reason"].create( + { + "name": "test closure reason", + "description": "test clopsure reason description", + } + ) + # ACT + reservation = self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "pms_property_id": self.pms_property1.id, + "reservation_type": "out", + "closure_reason_id": closure_reason.id, + "partner_name": "Install furniture", + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + + self.assertFalse( + reservation.partner_id, + "The partner of an out of service reservation should be False", + ) + + @freeze_time("2012-01-14") + def _test_create_partner_in_reservation(self): + """ + Check that a res_partner is created from a reservation. + ------------ + A reservation is created + and a res.partner + should also be created, which is what is checked after creating + the reservation. + """ + # ARRANGE + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=3) + self.id_category = self.env["res.partner.id_category"].create( + {"name": "DNI", "code": "D"} + ) + # ACT + reservation = self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "pms_property_id": self.pms_property1.id, + "partner_name": "Elis", + "email": "elis@mail.com", + "mobile": "61568547", + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + # ASSERT + self.assertTrue(reservation.partner_id, "The partner has not been created") + + @freeze_time("2012-01-14") + def test_auto_complete_partner_mobile(self): + """ + It is checked that the mobile field of the reservation + is correctly added to + a res.partner that exists in + the DB are put in the reservation. + -------------------- + A res.partner is created with the name, mobile and email fields. + Then it is verified that the mobile of the res.partner and that of + the reservation are the same. + """ + # ARRANGE + partner = self.env["res.partner"].create( + { + "name": "Enrique", + "mobile": "654667733", + "email": "enrique@example.com", + } + ) + self.id_category = self.env["res.partner.id_category"].create( + {"name": "DNI", "code": "D"} + ) + self.document_id = self.env["res.partner.id_number"].create( + { + "category_id": self.id_category.id, + "name": "61645604S", + "partner_id": partner.id, + } + ) + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=3) + # ACT + reservation = self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "pms_property_id": self.pms_property1.id, + "partner_id": partner.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + # ASSERT + self.assertEqual( + reservation.mobile, + partner.mobile, + "The partner mobile has not autocomplete in reservation", + ) + + @freeze_time("2012-01-14") + def test_auto_complete_partner_email(self): + """ + It is checked that the email field of the reservation + is correctly added to + a res.partner that exists in + the DB are put in the reservation. + -------------------- + A res.partner is created with the name, mobile and email fields. + The document_id is added to the res.partner. + Then it is verified that the email of the res.partner and that of + the reservation are the same. + """ + # ARRANGE + partner = self.env["res.partner"].create( + { + "name": "Simon", + "mobile": "654667733", + "email": "simon@example.com", + } + ) + self.id_category = self.env["res.partner.id_category"].create( + {"name": "DNI", "code": "D"} + ) + self.document_id = self.env["res.partner.id_number"].create( + { + "category_id": self.id_category.id, + "name": "74247377L", + "partner_id": partner.id, + } + ) + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=3) + # ACT + reservation = self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "pms_property_id": self.pms_property1.id, + "partner_id": partner.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + # ASSERT + self.assertEqual( + reservation.email, + partner.email, + "The partner email has not autocomplete in reservation", + ) + + @freeze_time("2012-01-14") + def test_is_possible_customer_by_email(self): + """ + It is checked that the field possible_existing_customer_ids + exists in a reservation with an email from a res.partner saved + in the DB. + ---------------- + A res.partner is created with the name and email fields. A reservation + is created by adding the same email as the res.partner. Then it is + checked that some possible_existing_customer_ids exists. + """ + # ARRANGE + partner = self.env["res.partner"].create( + { + "name": "Courtney Campbell", + "email": "courtney@example.com", + } + ) + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=3) + # ACT + reservation = self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "pms_property_id": self.pms_property1.id, + "partner_name": partner.name, + "email": partner.email, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + # ASSERT + self.assertTrue( + reservation.possible_existing_customer_ids, + "No customer found with this email", + ) + + @freeze_time("2012-01-14") + def test_is_possible_customer_by_mobile(self): + """ + It is checked that the field possible_existing_customer_ids + exists in a reservation with a mobile from a res.partner saved + in the DB. + ---------------- + A res.partner is created with the name and email fields. A reservation + is created by adding the same mobile as the res.partner. Then it is + checked that some possible_existing_customer_ids exists. + """ + # ARRANGE + partner = self.env["res.partner"].create( + { + "name": "Ledicia Sandoval", + "mobile": "615369231", + } + ) + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=3) + # ACT + reservation = self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "pms_property_id": self.pms_property1.id, + "partner_name": partner.name, + "mobile": partner.mobile, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + # ASSERT + self.assertTrue( + reservation.possible_existing_customer_ids, + "No customer found with this mobile", + ) + + @freeze_time("2012-01-14") + def test_add_possible_customer(self): + """ + Check that a partner was correctly added to the reservation + after launching the add_partner() method of the several partners wizard + --------------- + A res.partner is created with name, email and mobile. A reservation is + created with the email field equal to that of the res.partner created before. + The wizard is created with the reservation id and the partner added to the + possible_existing_customer_ids field. The add_partner method of the wizard + is launched and it is checked that the partner was correctly added to the + reservation. + """ + # ARRANGE + partner = self.env["res.partner"].create( + { + "name": "Serafín Rivas", + "email": "serafin@example.com", + "mobile": "60595595", + } + ) + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=3) + # ACT + reservation = self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "pms_property_id": self.pms_property1.id, + "partner_name": partner.name, + "email": partner.email, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + + several_partners_wizard = self.env["pms.several.partners.wizard"].create( + { + "reservation_id": reservation.id, + "possible_existing_customer_ids": [(6, 0, [partner.id])], + } + ) + + several_partners_wizard.add_partner() + # ASSERT + self.assertEqual( + reservation.partner_id.id, + partner.id, + "The partner was not added to the reservation ", + ) + + @freeze_time("2012-01-14") + def test_not_add_several_possibles_customers(self): + """ + Check that multiple partners cannot be added to a reservation + from the several partners wizard. + --------------- + Two res.partner are created with name, email and mobile. A reservation is + created with the email field equal to that of the partner1 created before. + The wizard is created with the reservation id and the two partners added to the + possible_existing_customer_ids field. The add_partner method of the wizard + is launched and it is verified that a Validation_Error was raised. + """ + # ARRANGE + partner1 = self.env["res.partner"].create( + { + "name": "Serafín Rivas", + "email": "serafin@example.com", + "mobile": "60595595", + } + ) + partner2 = self.env["res.partner"].create( + { + "name": "Simon", + "mobile": "654667733", + "email": "simon@example.com", + } + ) + + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=3) + reservation = self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "pms_property_id": self.pms_property1.id, + "partner_name": partner1.name, + "email": partner1.email, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + + several_partners_wizard = self.env["pms.several.partners.wizard"].create( + { + "reservation_id": reservation.id, + "possible_existing_customer_ids": [(6, 0, [partner1.id, partner2.id])], + } + ) + + # ACT AND ASSERT + with self.assertRaises( + ValidationError, + msg="Two partners cannot be added to the reservation", + ): + several_partners_wizard.add_partner() + + @freeze_time("2012-01-14") + def test_not_add_any_possibles_customers(self): + """ + Check that the possible_existing_customer_ids field of the several + partners wizard can be left empty and then launch the add_partner() + method of this wizard to add a partner in reservation. + --------------- + A reservation is created. The wizard is created without the + possible_existing_customer_ids field. The add_partner method of + the wizard is launched and it is verified that a Validation_Error + was raised. + """ + + # ARRANGE + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=3) + reservation = self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "pms_property_id": self.pms_property1.id, + "partner_name": "Rosa Costa", + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + + several_partners_wizard = self.env["pms.several.partners.wizard"].create( + { + "reservation_id": reservation.id, + } + ) + + # ACT AND ASSERT + with self.assertRaises( + ValidationError, + msg="A partner must be added to the reservation", + ): + several_partners_wizard.add_partner() + + @freeze_time("1991-11-10") + def test_commission_amount_with_board_service(self): + """ + Check if commission in reservation is correctly calculated + when reservation has services and board_services + + Create a service that isn't board service. + Create a service that is board service. + Create a reservation with that service and board_service. + + In this case when the reservation is made through an agency + that has a default commission, this commission is applied to the + price of the room and the price of services that correspond with + board service. + + """ + # ARRANGE + self.product1 = self.env["product.product"].create( + { + "name": "Product test1", + "per_day": True, + "consumed_on": "after", + "is_extra_bed": True, + } + ) + self.service = self.env["pms.service"].create( + { + "is_board_service": False, + "product_id": self.product1.id, + "pms_property_id": self.pms_property1.id, + } + ) + self.service.flush() + self.product_test1 = self.env["product.product"].create( + { + "name": "Test Product 1", + "per_day": True, + "consumed_on": "after", + } + ) + self.board_service_test = self.env["pms.board.service"].create( + { + "name": "Test Board Service", + "default_code": "TPS", + "pms_property_ids": [self.pms_property1.id], + } + ) + + self.env["pms.board.service.line"].create( + { + "pms_board_service_id": self.board_service_test.id, + "product_id": self.product_test1.id, + "amount": 8, + "adults": True, + } + ) + self.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": self.board_service_test.id, + "pms_property_id": self.pms_property1.id, + } + ) + checkin = fields.date.today() + checkout = checkin + datetime.timedelta(days=11) + reservation_vals = { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "agency_id": self.agency1.id, + "service_ids": [self.service.id], + "sale_channel_origin_id": self.sale_channel_direct.id, + } + # ACT + reservation = self.env["pms.reservation"].create(reservation_vals) + reservation.write( + { + "board_service_room_id": self.board_service_room_type.id, + } + ) + + self.commission = ( + reservation.price_total * self.agency1.default_commission / 100 + ) + for service in reservation.service_ids: + if service.is_board_service: + self.commission = ( + self.commission + + service.price_total * self.agency1.default_commission / 100 + ) + # ASSERT + self.assertEqual( + self.commission, + reservation.commission_amount, + "Reservation commission is wrong", + ) + + @freeze_time("2012-01-14") + def test_closure_reason_out_of_service_mandatory_not(self): + """ + Ouf of service reservation should contain a closure reason id. + ------------- + Create a reservation of type out of service and check if there's no + closure reason id should raises an exception. + """ + # ARRANGE + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=1) + # ACT & ASSERT + with self.assertRaises( + ValidationError, + msg="The reservation has been created and it shouldn't, " + "because it doesn't have a closure reason.", + ): + self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "reservation_type": "out", + } + ) + + @freeze_time("2012-01-14") + def test_closure_reason_out_of_service_mandatory(self): + """ + Ouf of service reservation should contain a closure reason id. + ------------- + Create a reservation of type out of service and with a closure reason. + """ + # ARRANGE + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=1) + + closure_reason = self.env["room.closure.reason"].create( + { + "name": "Room revision", + "description": "Revision of lights, " + "fire extinguishers, smoke detectors and " + "emergency lights", + } + ) + # ACT + reservation_out = self.env["pms.reservation"].create( + { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "reservation_type": "out", + "closure_reason_id": closure_reason, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + ) + # ASSERT + self.assertTrue( + reservation_out.closure_reason_id, + "The out of service reservation should be created properly with " + "a closure reason.", + ) + + # tests for several sale channels in reservation + @freeze_time("2000-11-10") + def test_reservation_sale_channel_origin_in_reservation_lines(self): + """ + Check that reservation_lines have sale_channel_id + corresponding to sale_channel_origin_id of their reservation + + When a reservation was created with a sale channel, it corresponds + to the sale_channel_origin. + Reservation lines will have as sale_channel_id the sale_channel_origin_id + of reservation when creating them + + """ + # ARRANGE + checkin = fields.date.today() + checkout = checkin + datetime.timedelta(days=3) + reservation_vals = { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + + # ACT + reservation = self.env["pms.reservation"].create(reservation_vals) + + # ASSERT + self.assertEqual( + reservation.sale_channel_origin_id, + reservation.reservation_line_ids.mapped("sale_channel_id"), + "Sale channel of reservation lines must be the same that their reservation", + ) + + @freeze_time("2000-10-10") + def test_reservation_sale_channel_origin_in_folio(self): + """ + Check that folio have sale_channel_origin_id + corresponding to sale_channel_origin_id of the reservation + that was created before the folio + + When a reservation was created with a sale channel, it corresponds + to the sale_channel_origin. + If reservation didn't have folio previously, the folio to be created + will have the same sale_channel_origin as the reservation + + """ + # ARRANGE + checkin = fields.date.today() + checkout = checkin + datetime.timedelta(days=3) + reservation_vals = { + "checkin": checkin, + "checkout": checkout, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + + # ACT + reservation = self.env["pms.reservation"].create(reservation_vals) + + # ASSERT + self.assertEqual( + reservation.sale_channel_origin_id, + reservation.folio_id.sale_channel_origin_id, + "Sale channel of folio must be the same that it reservation", + ) + + @freeze_time("2001-10-15") + def test_reservation_sale_channel_origin_of_folio(self): + """ + Check that the reservation has sale_channel_origin_id + as the folio sale_channel_origin_id in + which reservation was created when a folio has already + another reservations. + + Testing whether it works when the folio sale_channel_origin_id + is given by a previously created reservation + + When a reservation is created on a folio + that already has a sale_channel_origin + that reservation will have the same sale_channel_origin + + """ + # ARRANGE + reservation_vals = { + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + reservation1 = self.env["pms.reservation"].create(reservation_vals) + # ACT + reservation2 = self.env["pms.reservation"].create( + { + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "folio_id": reservation1.folio_id.id, + } + ) + # ASSERT + self.assertEqual( + reservation1.sale_channel_origin_id.id, + reservation2.sale_channel_origin_id.id, + "Sale channel of reservations must be the same", + ) + + @freeze_time("2000-10-19") + def test_reservation_lines_same_sale_channel(self): + """ + Check if sale_channel_ids of reservation correspond to + sale_channel_id of its reservation. + + In this case, the reservation has several reservation_lines + with the same sale_channel_id. Reservation lines are created + with sale_channel_origin_id of the reservation and haven't been + modified. + + """ + # ARRANGE + reservation_vals = { + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=4), + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + # ACT + reservation1 = self.env["pms.reservation"].create(reservation_vals) + + # ASSERT + self.assertEqual( + reservation1.sale_channel_ids, + reservation1.reservation_line_ids.mapped("sale_channel_id"), + "Sale_channel_ids of reservation must be the same as " + "sale channels of its reservation lines", + ) + + @freeze_time("2000-10-24") + def test_reservation_sale_channel_origin_change_check_lines(self): + """ + Check that sale_channel_id of reservation_lines changes when + sale_channel_origin_id of its reservation has changed + """ + # ARRANGE + sale_channel_direct2 = self.env["pms.sale.channel"].create( + { + "name": "sale channel 2", + "channel_type": "direct", + } + ) + reservation_vals = { + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=4), + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + reservation1 = self.env["pms.reservation"].create(reservation_vals) + + # ACT + reservation1.sale_channel_origin_id = sale_channel_direct2.id + + # ASSERT + self.assertEqual( + reservation1.sale_channel_origin_id, + reservation1.reservation_line_ids.mapped("sale_channel_id"), + "Sale_channel_id of reservation lines must also be changed", + ) + + @freeze_time("2000-10-29") + def test_reservation_lines_not_change_sale_channel(self): + """ + Check that when changing sale_channel_origin_id of a reservation, + reservation lines that didn't have the same sale_channel_id didn't + change it + + Reservation1: + --> sale_channel_origin_id : sale_channel1.id + --> reservation_lines: + --> 1: sale_channel_id: sale_channel1.id + --> 2: sale_channel_id: sale_channel1.id + --> 3: sale_channel_id: sale_channel1.id + --> 4: sale_channel_id: sale_channel_phone.id + + Change reservation1.sale_channel_origin_id = sale_channel_mail.id + + Expected result: + Reservation1: + --> sale_channel_origin_id : sale_channel_mail.id + --> reservation_lines: + --> 1: sale_channel_id: sale_channel_mail.id + --> 2: sale_channel_id: sale_channel_mail.id + --> 3: sale_channel_id: sale_channel_mail.id + --> 4: sale_channel_id: sale_channel_phone.id + + In short, sale channel of those reservations lines of the reservation + that didn't coincide with sale chanel origin that has been modified, + shouldn't be changed. That is, the last reservation_line must have + sale_channel_id = sale_channel_phone + """ + # ARRANGE + sale_channel_phone = self.env["pms.sale.channel"].create( + { + "name": "phone", + "channel_type": "direct", + } + ) + sale_channel_mail = self.env["pms.sale.channel"].create( + { + "name": "mail", + "channel_type": "direct", + } + ) + reservation_vals = { + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=4), + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + reservation1 = self.env["pms.reservation"].create(reservation_vals) + + # ACT + reservation_lines = reservation1.reservation_line_ids + reservation_lines[ + len(reservation_lines) - 1 + ].sale_channel_id = sale_channel_phone.id + + reservation1.sale_channel_origin_id = sale_channel_mail + + # ASSERT + self.assertNotEqual( + reservation1.sale_channel_origin_id, + reservation_lines[len(reservation_lines) - 1].sale_channel_id, + "Sale_channel_id of that reservation line shouldn't have changed", + ) + + @freeze_time("2000-11-29") + def test_several_sale_channel_in_lines(self): + """ + Check that when a reservation has more than one sale_channel_id + in its reservation_lines, sale_channel_ids of reservation is well + calculated + """ + # ARRANGE + sale_channel_phone = self.env["pms.sale.channel"].create( + { + "name": "phone", + "channel_type": "direct", + } + ) + reservation_vals = { + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=4), + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + reservation1 = self.env["pms.reservation"].create(reservation_vals) + + # ACT + reservation_lines = reservation1.reservation_line_ids + reservation_lines[0].sale_channel_id = sale_channel_phone.id + + expected_sale_channels = [] + for line in reservation_lines: + expected_sale_channels.append(line.sale_channel_id.id) + + # ASSERT + self.assertItemsEqual( + reservation1.sale_channel_ids.ids, + list(set(expected_sale_channels)), + "Sale_channel_ids of that reservation must match those of its lines", + ) + + @freeze_time("2000-12-01") + def test_reservation_no_sale_channel_origin(self): + """ + Check that you can't create a reservation without sale_channel_origin + """ + # ACT & ASSERT + with self.assertRaises( + ValidationError, + msg="Error, it has been allowed to create a reservation without sale channel", + ): + self.env["pms.reservation"].create( + { + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=4), + } + ) + + @freeze_time("2000-12-01") + def test_one_reservation_change_sale_channel_origin(self): + """ + Check that when changing the sale_channel_origin of a reservation, + sale_channel_origin of its folio changes if folio only has one reservation + """ + # ARRANGE + sale_channel_phone = self.env["pms.sale.channel"].create( + { + "name": "phone", + "channel_type": "direct", + } + ) + reservation_vals = { + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=4), + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct.id, + } + reservation1 = self.env["pms.reservation"].create(reservation_vals) + + # ACT + reservation1.sale_channel_origin_id = sale_channel_phone.id + + # ASSERT + self.assertEqual( + reservation1.folio_id.sale_channel_origin_id, + reservation1.sale_channel_origin_id, + "Sale_channel_origin_id of folio must be the same as " + "sale_channel_origin of rservation", + ) + + # TEMPORAL UNABLE (_check_lines_with_sale_channel_id in pms_reservation.py + # unable to allow updagrade version) + # @freeze_time("2000-12-10") + # def test_check_sale_channel_origin_in_reservation_lines(self): + # """ + # Check that a reservation has at least one reservation_line woth the + # same sale_channel_id as its sale_channel_origin_id + # """ + # # ARRANGE + # sale_channel_phone = self.env["pms.sale.channel"].create( + # { + # "name": "phone", + # "channel_type": "direct", + # } + # ) + # reservation_vals = { + # "checkin": datetime.datetime.now(), + # "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + # "room_type_id": self.room_type_double.id, + # "partner_id": self.partner1.id, + # "pms_property_id": self.pms_property1.id, + # "sale_channel_origin_id": self.sale_channel_direct.id, + # } + # reservation1 = self.env["pms.reservation"].create(reservation_vals) + # reservation1.fetch() + # # ACT & ASSERT + # with self.assertRaises( + # ValidationError, + # msg=""" + # Error, there cannot be a reservation + # in which at least one of its reservation + # """ + # "lines doesn't have as sale_channel_id the sale_channel_origin_id of reservation", + # ): + # reservation1.reservation_line_ids.write( + # {"sale_channel_id": sale_channel_phone} + # ) + # reservation1.flush() diff --git a/pms/tests/test_pms_room.py b/pms/tests/test_pms_room.py new file mode 100644 index 0000000000..4c006cb87b --- /dev/null +++ b/pms/tests/test_pms_room.py @@ -0,0 +1,343 @@ +from psycopg2 import IntegrityError + +from odoo.exceptions import ValidationError +from odoo.tools import mute_logger + +from .common import TestPms + + +class TestPmsRoom(TestPms): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.pms_property2 = cls.env["pms.property"].create( + { + "name": "Property_2", + "company_id": cls.company1.id, + "default_pricelist_id": cls.pricelist1.id, + } + ) + + cls.room_type1 = cls.env["pms.room.type"].create( + { + "pms_property_ids": [cls.pms_property1.id, cls.pms_property2.id], + "name": "Single", + "default_code": "SIN", + "class_id": cls.room_type_class1.id, + "list_price": 30, + } + ) + + @mute_logger("odoo.sql_db") + def test_room_name_uniqueness_by_property(self): + """ + Check that there are no two rooms with the same name in the same property + PRE: - room1 'Room 101' exists + - room1 has pms_property1 + ACT: - create a new room2 + - room2 has name 'Room 101' + - room2 has pms_property1 + POST: - Integrity error: already exists another room + with the same name on the same property + - room2 not created + """ + # ARRANGE + self.env["pms.room"].create( + { + "name": "Room 101", + "pms_property_id": self.pms_property1.id, + "room_type_id": self.room_type1.id, + } + ) + # ACT & ASSERT + with self.assertRaises( + IntegrityError, + msg="The room should not be created if its name is equal " + "to another room that belongs to the same property.", + ): + self.env["pms.room"].create( + { + "name": "Room 101", + "pms_property_id": self.pms_property1.id, + "room_type_id": self.room_type1.id, + } + ) + + def test_room_name_duplicated_different_property(self): + """ + Check that two rooms with the same name can exist in multiple properties + PRE: - room1 'Room 101' exists + - room1 has pms_property1 + ACT: - create a new room2 + - room2 has name 'Room 101' + - room2 has pms_property2 + POST: - room2 created + """ + # ARRANGE + self.env["pms.room"].create( + { + "name": "Room 101", + "pms_property_id": self.pms_property1.id, + "room_type_id": self.room_type1.id, + } + ) + # ACT & ASSERT + try: + self.env["pms.room"].create( + { + "name": "Room 101", + "pms_property_id": self.pms_property2.id, + "room_type_id": self.room_type1.id, + } + ) + except IntegrityError: + self.fail( + "The room should be created even if its name is equal " + "to another room, but that room not belongs to the same property." + ) + + def test_display_name_room(self): + """ + Check that the display_name field of a room is as expected. + ------------ + A room is created and then it is checked that the display name + field of this is composed of: + room.name [room_type.default_code] + """ + self.room1 = self.env["pms.room"].create( + { + "name": "Room 101", + "pms_property_id": self.pms_property1.id, + "room_type_id": self.room_type1.id, + } + ) + expected_display_name = "%s [%s]" % ( + self.room1.name, + self.room_type1.default_code, + ) + self.assertEqual( + self.room1.display_name, + expected_display_name, + "The display name of the room is not as expected", + ) + + def test_display_name_room_with_amenity(self): + """ + Check that the display_name field of a room with one amenity + is as expected. + ------------ + A amenity is created with default code and with is_add_code_room_name + field as True. A room is created in which the amenity created before + is added in the room_amenity_ids field and then it is verified that + the display name field of this is composed of: + room.name [room_type.default_code] amenity.default_code + """ + self.amenity_type1 = self.env["pms.amenity.type"].create( + { + "name": "Amenity Type 1", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + ) + self.amenity1 = self.env["pms.amenity"].create( + { + "name": "Amenity 1", + "pms_amenity_type_id": self.amenity_type1.id, + "default_code": "A1", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + "is_add_code_room_name": True, + } + ) + self.room1 = self.env["pms.room"].create( + { + "name": "Room 101", + "pms_property_id": self.pms_property1.id, + "room_type_id": self.room_type1.id, + "room_amenity_ids": [(6, 0, [self.amenity1.id])], + } + ) + expected_display_name = "%s [%s] %s" % ( + self.room1.name, + self.room_type1.default_code, + self.amenity1.default_code, + ) + self.assertEqual( + self.room1.display_name, + expected_display_name, + "The display name of the room is not as expected", + ) + + def test_display_name_room_with_several_amenities(self): + """ + Check that the display_name field of a room with several amenities + is as expected. + ------------ + Two amenities are created with diferent default code and with is_add_code_room_name + field as True. A room is created in which the amenities created before are added in + the room_amenity_ids field and then it is verified that the display name field of this + is composed of: + room.name [room_type.default_code] amenity1.default_code amenity2.default_code + """ + self.amenity_type1 = self.env["pms.amenity.type"].create( + { + "name": "Amenity Type 1", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + ) + self.amenity1 = self.env["pms.amenity"].create( + { + "name": "Amenity 1", + "pms_amenity_type_id": self.amenity_type1.id, + "default_code": "A1", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + "is_add_code_room_name": True, + } + ) + self.amenity2 = self.env["pms.amenity"].create( + { + "name": "Amenity 2", + "pms_amenity_type_id": self.amenity_type1.id, + "default_code": "B1", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + "is_add_code_room_name": True, + } + ) + self.room1 = self.env["pms.room"].create( + { + "name": "Room 101", + "pms_property_id": self.pms_property1.id, + "room_type_id": self.room_type1.id, + "room_amenity_ids": [(6, 0, [self.amenity1.id, self.amenity2.id])], + } + ) + expected_display_name = "%s [%s] %s %s" % ( + self.room1.name, + self.room_type1.default_code, + self.amenity1.default_code, + self.amenity2.default_code, + ) + self.assertEqual( + self.room1.display_name, + expected_display_name, + "The display name of the room is not as expected", + ) + + def test_short_name_room_name_gt_4(self): + """ + It checks through subtest that the short names of the + rooms are correctly established when the names of these + exceed 4 characters. + ------------------------------------------------------- + First a room_type (Sweet Imperial) is created. Then 6 rooms + are created with the name Sweet Imperial + room number. Finally + in a loop we check that the short name of the rooms was set + correctly: 'SW01, SW02, SW03...' + """ + self.room_type2 = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id], + "name": "Sweet Imperial", + "default_code": "SWI", + "class_id": self.room_type_class1.id, + "list_price": 100, + } + ) + rooms = [] + self.room1 = self.env["pms.room"].create( + { + "name": "Sweet Imperial 101", + "pms_property_id": self.pms_property1.id, + "room_type_id": self.room_type2.id, + } + ) + rooms.append(self.room1) + self.room2 = self.env["pms.room"].create( + { + "name": "Sweet Imperial 102", + "pms_property_id": self.pms_property1.id, + "room_type_id": self.room_type2.id, + } + ) + rooms.append(self.room2) + self.room3 = self.env["pms.room"].create( + { + "name": "Sweet Imperial 103", + "pms_property_id": self.pms_property1.id, + "room_type_id": self.room_type2.id, + } + ) + rooms.append(self.room3) + self.room4 = self.env["pms.room"].create( + { + "name": "Sweet Imperial 104", + "pms_property_id": self.pms_property1.id, + "room_type_id": self.room_type2.id, + } + ) + rooms.append(self.room4) + self.room5 = self.env["pms.room"].create( + { + "name": "Sweet Imperial 105", + "pms_property_id": self.pms_property1.id, + "room_type_id": self.room_type2.id, + } + ) + rooms.append(self.room5) + self.room6 = self.env["pms.room"].create( + { + "name": "Sweet Imperial 106", + "pms_property_id": self.pms_property1.id, + "room_type_id": self.room_type2.id, + } + ) + rooms.append(self.room6) + for index, room in enumerate(rooms, start=1): + with self.subTest(room): + self.assertEqual( + room.short_name, + "SW0" + str(index), + "The short name of the room should be SW0" + str(index), + ) + + def test_short_name_room_name_lt_4(self): + """ + Checks that the short name of a room is equal to the name + when it does not exceed 4 characters. + --------------------------------------------------------- + A room is created with a name less than 4 characters (101). + Then it is verified that the short name and the name of the + room are the same. + """ + self.room1 = self.env["pms.room"].create( + { + "name": "101", + "pms_property_id": self.pms_property1.id, + "room_type_id": self.room_type1.id, + } + ) + self.assertEqual( + self.room1.short_name, + self.room1.name, + "The short name of the room should be equal to the name of the room", + ) + + def test_short_name_gt_4_constraint(self): + """ + Check that the short name of a room cannot exceed 4 characters. + -------------------------------------------------------------- + A room named 201 is created. Afterwards, it is verified that a + ValidationError is thrown when trying to change the short name + of that room to 'SIN-201'. + """ + self.room1 = self.env["pms.room"].create( + { + "name": "201", + "pms_property_id": self.pms_property1.id, + "room_type_id": self.room_type1.id, + } + ) + + with self.assertRaises( + ValidationError, + msg="The short_name of the room should not be able to be write.", + ): + self.room1.write({"short_name": "SIN-201"}) diff --git a/pms/tests/test_pms_room_type.py b/pms/tests/test_pms_room_type.py new file mode 100644 index 0000000000..bb25d6a19f --- /dev/null +++ b/pms/tests/test_pms_room_type.py @@ -0,0 +1,886 @@ +# Copyright 2021 Eric Antones +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.exceptions import UserError, ValidationError + +from .common import TestPms + + +class TestRoomType(TestPms): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.pms_property2 = cls.env["pms.property"].create( + { + "name": "Property 2", + "company_id": cls.company1.id, + "default_pricelist_id": cls.pricelist1.id, + } + ) + cls.company2 = cls.env["res.company"].create( + { + "name": "Company 2", + } + ) + + cls.pms_property3 = cls.env["pms.property"].create( + { + "name": "Property 3", + "company_id": cls.company2.id, + "default_pricelist_id": cls.pricelist1.id, + } + ) + + def test_create_room_type_consistency_company(self): + """ + Create a room type with a company (1) consistent with the company property (1). + Creation should be successful. + + PRE: - room_type1 does not exists + ACT: - create a new room_type1 room + - room_type1 has code c1 + - room_type1 has property pms_property1 + - pms_property1 has company company1 + - room_type1 has company company1 + POST: - room_type1 created + """ + # ARRANGE & ACT + new_room_type = self.env["pms.room.type"].create( + { + "name": "Room type 1", + "default_code": "c1", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + "company_id": self.company1.id, + "class_id": self.room_type_class1.id, + } + ) + # ASSERT + self.assertTrue(new_room_type.id, "Room type not created when it should") + + def test_create_room_type_inconsistency_company(self): + """ + Create a room type with inconsistency between company (1) + and company property (1). + The creation should fail. + + PRE: - room_type1 does not exists + ACT: - create a new room_type1 room + - room_type1 has code c1 + - room_type1 has property pms_property1 + - pms_property1 has company1 + - room_type1 has company2 + POST: - Integrity error, pms_property1 has company1 and room type company2 + - room_type1 not created + """ + # ARRANGE & ACT & ASSERT + with self.assertRaises( + UserError, msg="The room type has been created and it shouldn't" + ): + # room_type1 + self.env["pms.room.type"].create( + { + "name": "Room type 1", + "default_code": "c1", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + "company_id": self.company2.id, + "class_id": self.room_type_class1.id, + } + ) + + def test_create_room_type_inconsistency_companies(self): + """ + Create a room type with inconsistency between company (1) + and company properties (several). + The creation should fail. + + PRE: - room_type1 does not exists + ACT: - create a new room_type1 room + - room_type1 has code c1 + - room_type1 has property pms_property1 and pms_property3 + - pms_property1 has company company1 + - pms_property3 has company2 + - room_type1 has company2 + POST: - Integrity error, pms_property1 has company1 and room type company2 + - room_type1 not created + """ + # ARRANGE & ACT & ASSERT + with self.assertRaises( + UserError, msg="The room type has been created and it shouldn't" + ): + # room_type1 + self.env["pms.room.type"].create( + { + "name": "Room type 1", + "default_code": "c1", + "pms_property_ids": [ + (6, 0, [self.pms_property1.id, self.pms_property3.id]) + ], + "company_id": self.company2.id, + "class_id": self.room_type_class1.id, + } + ) + + def test_create_room_type_consistency_companies(self): + """ + Create a room type with consistency between companies (all) + and company properties (2). + Creation should be successful. + + PRE: - room_type1 does not exists + ACT: - create a new room_type1 + - room_type1 has code c1 + - room_type1 has property pms_property1 and pms_property3 + - pms_property1 has company company1 + - pms_property3 has company2 + - room_type1 has no company + POST: - room_type1 created + """ + # ARRANGE & ACT + new_room_type = self.env["pms.room.type"].create( + { + "name": "Room type 1", + "default_code": "c1", + "pms_property_ids": [ + (6, 0, [self.pms_property1.id, self.pms_property3.id]) + ], + "company_id": False, + "class_id": self.room_type_class1.id, + } + ) + # ASSERT + self.assertTrue(new_room_type.id, "Room type not created when it should") + + # external integrity + def test_create_room_type_inconsistency_all_companies(self): + """ + Create a room type for 1 company and 1 property. + Try to create a room type for all the companies. + The creation should fail. + + PRE: - room type room_type1 exists + - room_type1 has code c1 + - room_type1 has property pms_property1 + - pms_property1 has company company1 + - room_type1 has no company + ACT: - create a new room_type2 room + - room_type2 has code c1 + - room_type2 has property pms_property1 + - pms_property1 has company company1 + - room_type2 has no company + POST: - Integrity error: the room type already exists + - room_type2 not created + """ + # ARRANGE + # room_type1 + self.env["pms.room.type"].create( + { + "name": "Room type 1", + "default_code": "c1", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + "company_id": False, + "class_id": self.room_type_class1.id, + } + ) + # ACT & ASSERT + with self.assertRaises( + ValidationError, msg="The room type has been created and it shouldn't" + ): + # room_type2 + self.env["pms.room.type"].create( + { + "name": "Room type 2", + "default_code": "c1", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + "company_id": False, + "class_id": self.room_type_class1.id, + } + ) + + def test_create_room_type_inconsistency_code_company(self): + """ + Create a room type for all the companies and for one property. + Try to create a room type with same code and same property but + for all companies. + The creation should fail. + + PRE: - room type room_type1 exists + - room_type1 has code c1 + - room_type1 has property pms_property1 + - pms_property1 has company company1 + - room_type1 has no company + ACT: - create a new room_type2 room + - room_type2 has code c1 + - room_type2 has property pms_property1 + - pms_property1 has company company1 + - room_type2 has company company1 + POST: - Integrity error: the room type already exists + - room_type2 not created + """ + # ARRANGE + # room_type1 + self.env["pms.room.type"].create( + { + "name": "Room type 1", + "default_code": "c1", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + "company_id": False, + "class_id": self.room_type_class1.id, + } + ) + + # ACT & ASSERT + with self.assertRaises( + ValidationError, msg="The room type has been created and it shouldn't" + ): + # room_type2 + self.env["pms.room.type"].create( + { + "name": "Room type 2", + "default_code": "c1", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + "company_id": self.company1.id, + "class_id": self.room_type_class1.id, + } + ) + + def test_create_room_type_inconsistency_code_companies(self): + """ + Create a room type for 1 property and 1 company. + Try to create a room type with same code and 3 propertys + belonging to 2 different companies. + The creation should fail. + + PRE: - room type room_type1 exists + - room_type1 has code c1 + - room_type1 has property pms_property1 + - pms_property1 has company company1 + - room_type1 has company company1 + ACT: - create a new room_type2 room + - room_type2 has code c1 + - room_type2 has property pms_property1, pms_property2, pms_property3 + - pms_property1, pms_property2 has company company1 + - pms_property3 has company2 + - room_type2 has no company + POST: - Integrity error: the room type already exists + - room_type not created + """ + # ARRANGE + # room_type1 + self.env["pms.room.type"].create( + { + "name": "Room type 1", + "default_code": "c1", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + "company_id": self.company1.id, + "class_id": self.room_type_class1.id, + } + ) + # ACT & ASSERT + with self.assertRaises( + ValidationError, msg="The room type has been created and it shouldn't" + ): + # room_type2 + self.env["pms.room.type"].create( + { + "name": "Room type 2", + "default_code": "c1", + "pms_property_ids": [ + ( + 6, + 0, + [ + self.pms_property1.id, + self.pms_property2.id, + self.pms_property3.id, + ], + ) + ], + "company_id": False, + "class_id": self.room_type_class1.id, + } + ) + + def test_get_room_type_by_property_first(self): + """ + Room type exists for all the companies and 2 properties. + Search for property of existing room type. + The method should return the existing room type. + + PRE: - room type room_type1 exists + - room_type1 has code c1 + - room_type1 with 2 properties pms_property1 and pms_property2 + - pms_property1 and pms_property2 have the same company company1 + - room_type1 has no company + ACT: - search room type with code c1 and pms_property1 + - pms_property1 has company company1 + POST: - only room_type1 room type found + """ + # ARRANGE + room_type1 = self.env["pms.room.type"].create( + { + "name": "Room type 1", + "default_code": "c1", + "pms_property_ids": [ + (6, 0, [self.pms_property1.id, self.pms_property3.id]) + ], + "company_id": False, + "class_id": self.room_type_class1.id, + } + ) + # ACT + room_types = self.env["pms.room.type"].get_room_types_by_property( + self.pms_property1.id, "c1" + ) + # ASSERT + self.assertEqual(room_types.id, room_type1.id, "Expected room type not found") + + def test_get_room_type_by_property_second(self): + """ + Room type exists for all the companies and 2 properties. + Search for 2nd property of existing room type. + The method should return existing room type. + + PRE: - room type room_type1 exists + - room_type1 has code c1 + - room_type1 with 2 properties pms_property1 and pms_property2 + - pms_property1 and pms_property2 have same company company1 + - room_type1 has no company + ACT: - search room type with code c1 and property pms_property3 + - pms_property3 have company2 + POST: - no room type found + """ + # ARRANGE + # room_type1 + self.env["pms.room.type"].create( + { + "name": "Room type 1", + "default_code": "c1", + "pms_property_ids": [ + (6, 0, [self.pms_property1.id, self.pms_property2.id]) + ], + "company_id": False, + "class_id": self.room_type_class1.id, + } + ) + + # ACT + room_types = self.env["pms.room.type"].get_room_types_by_property( + self.pms_property3.id, "c1" + ) + + # ASSERT + self.assertFalse(room_types, "Room type found but it should have not found any") + + def test_get_room_type_by_property_existing_all_same_company(self): + """ + Room type exists for 1 company and for all properties. + Search for one specific property belonging to same company + as the existing room type. + The method should return existing room type. + + PRE: - room type r1 exists + - room_type1 has code c1 + - room_type1 properties are null + - room_type1 company is company1 + ACT: - search room type with code c1 and pms_property1 + - pms_property1 have company company1 + POST: - only rroom_type1 room type found + """ + # ARRANGE + room_type1 = self.env["pms.room.type"].create( + { + "name": "Room type 1", + "default_code": "c1", + "pms_property_ids": False, + "company_id": self.company1.id, + "class_id": self.room_type_class1.id, + } + ) + # ACT + room_types = self.env["pms.room.type"].get_room_types_by_property( + self.pms_property1.id, "c1" + ) + # ASSERT + self.assertEqual(room_types.id, room_type1.id, "Expected room type not found") + + def test_get_room_type_by_property_existing_all_diff_company(self): + """ + Room type exists for 1 company and all the properties. + Search for property different than existing room type. + The method shouldn't return results. + + PRE: - room type room_type1 exists + - room_type1 has code c1 + - room_type1 properties are null + - room_type1 company is company1 + ACT: - search room type with code c1 and pms_property3 + - pms_property3 have company2 + POST: - no room type found + """ + # ARRANGE + # room_type1 + self.env["pms.room.type"].create( + { + "name": "Room type r1", + "default_code": "c1", + "pms_property_ids": False, + "company_id": self.company1.id, + "class_id": self.room_type_class1.id, + } + ) + + # ACT + room_types = self.env["pms.room.type"].get_room_types_by_property( + self.pms_property3.id, "c1" + ) + + # ASSERT + self.assertFalse(room_types, "Room type found but it should have not found any") + + # tests with more than one room type + def test_get_room_type_by_property_existing_several_match_prop(self): + """ + Room type 1 exists for all companies and 2 properties. + Room type 2 exists for all companies and properties. + Search for same property as one of the 1st room type created. + The method should return the 1st room type created. + + PRE: - room type room_type1 exists + - room_type1 has code c1 + - room_type1 with 2 properties pms_property1 and pms_property2 + - pms_property1 and pms_property2 have the same company company1 + - room_type1 has no company + - room type room_type2 exists + - room_type2 has code c1 + - room_type2 has no properties + - room_type2 has no company + ACT: - search room type with code c1 and property pms_property1 + - pms_property1 have company company1 + POST: - only room_type1 room type found + """ + # ARRANGE + room_type1 = self.env["pms.room.type"].create( + { + "name": "Room type 1", + "default_code": "c1", + "pms_property_ids": [ + (6, 0, [self.pms_property1.id, self.pms_property3.id]) + ], + "company_id": False, + "class_id": self.room_type_class1.id, + } + ) + # room_type2 + self.env["pms.room.type"].create( + { + "name": "Room type 2", + "default_code": "c1", + "pms_property_ids": False, + "company_id": False, + "class_id": self.room_type_class1.id, + } + ) + + # ACT + room_types = self.env["pms.room.type"].get_room_types_by_property( + self.pms_property1.id, "c1" + ) + + # ASSERT + self.assertEqual(room_types.id, room_type1.id, "Expected room type not found") + + def test_get_room_type_by_property_diff_company(self): + """ + Room type 1 exists for all companies and one property. + Room type 2 exists for all companies and properties. + Search for property different than the 1st room type created + and belonging to different company. + The method should return the 2nd room type created. + + PRE: - room type r1 exists + - room_type1 has code c1 + - room_type1 has property pms_property1 + - pms_property1 have the company company1 + - room_type1 has no company + - room type room_type2 exists + - room_type2 has code c1 + - room_type2 has no properties + - room_type2 has no company + ACT: - search room type with code c1 and property pms_property2 + - pms_property2 have company company1 + POST: - only room_type1 room type found + """ + # ARRANGE + # room_type1 + self.env["pms.room.type"].create( + { + "name": "Room type 1", + "default_code": "c1", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + "company_id": False, + "class_id": self.room_type_class1.id, + } + ) + room_type2 = self.env["pms.room.type"].create( + { + "name": "Room type 2", + "default_code": "c1", + "pms_property_ids": False, + "company_id": False, + "class_id": self.room_type_class1.id, + } + ) + + # ACT + room_types = self.env["pms.room.type"].get_room_types_by_property( + self.pms_property2.id, "c1" + ) + + # ASSERT + self.assertEqual(room_types.id, room_type2.id, "Expected room type not found") + + def test_get_room_type_by_property_same_company(self): + """ + Room type 1 exists for all companies and one property. + Room type 2 exists for all companies and properties. + Search for property different than the 1st room type created + and belonging to same company. + The method should return the 2nd room type created. + + PRE: - room type room_type1 exists + - room_type1 has code c1 + - room_type1 has property pms_property1 + - pms_property1 have the company company1 + - room_type1 has no company + - room type room_type2 exists + - room_type2 has code c1 + - room_type2 has no properties + - room_type2 has no company + ACT: - search room type with code c1 and pms_property3 + - pms_property3 have company2 + POST: - only room_type2 room type found + """ + # ARRANGE + # room_type1 + self.env["pms.room.type"].create( + { + "name": "Room type 1", + "default_code": "c1", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + "company_id": False, + "class_id": self.room_type_class1.id, + } + ) + room_type2 = self.env["pms.room.type"].create( + { + "name": "Room type 2", + "default_code": "c1", + "pms_property_ids": False, + "company_id": False, + "class_id": self.room_type_class1.id, + } + ) + + # ACT + room_types = self.env["pms.room.type"].get_room_types_by_property( + self.pms_property3.id, "c1" + ) + + # ASSERT + self.assertEqual(room_types.id, room_type2.id, "Expected room type not found") + + def test_get_room_type_by_property_same_company_prop_not_found(self): + """ + Room type 1 exists for all companies and one property. + Room type 2 exists for one company and for all properties. + Search for property different than the + 1st room type created but belonging to same company. + The method shouldn't return results. + + PRE: - room_type1 exists + - room_type1 has code c1 + - room_type1 has property pms_property1 + - pms_property1 have the company company1 + - room_type1 has no company + - room_type2 exists + - room_type2 has code c1 + - room_type2 has no properties + - room_type2 has company company1 + ACT: - search room type with code c1 and pms_property3 + - pms_property3 have company2 + POST: - no room type found + """ + # ARRANGE + # room_type1 + self.env["pms.room.type"].create( + { + "name": "Room type 1", + "default_code": "c1", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + "company_id": False, + "class_id": self.room_type_class1.id, + } + ) + # room_type2 + self.env["pms.room.type"].create( + { + "name": "Room type 2", + "default_code": "c1", + "pms_property_ids": False, + "company_id": self.company1.id, + "class_id": self.room_type_class1.id, + } + ) + + # ACT + room_types = self.env["pms.room.type"].get_room_types_by_property( + self.pms_property3.id, "c1" + ) + + # ASSERT + self.assertFalse(room_types, "Room type found but it should have not found any") + + def test_get_room_type_by_property_same_company_prop(self): + """ + Room type 1 exists for all companies and for one property. + Room type 2 exists for one company and for all properties. + Search for property belonging to the same company as + 2nd room type created. + The method should return 2nd existing room type. + + PRE: - room_type1 exists + - room_type1 has code c1 + - room_type1 has property pms_property1 + - pms_property1 have the company company1 + - room_type1 has no company + - room_type2 exists + - room_type2 has code c1 + - room_type2 has no properties + - room_type2 has company2 + ACT: - search room type with code c1 and pms_property3 + - pms_property3 have company2 + POST: - room_type2 room type found + """ + # ARRANGE + # room_type1 + self.env["pms.room.type"].create( + { + "name": "Room type 1", + "default_code": "c1", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + "company_id": False, + "class_id": self.room_type_class1.id, + } + ) + room_type2 = self.env["pms.room.type"].create( + { + "name": "Room type 2", + "default_code": "c1", + "pms_property_ids": False, + "company_id": self.company2.id, + "class_id": self.room_type_class1.id, + } + ) + # ACT + room_types = self.env["pms.room.type"].get_room_types_by_property( + self.pms_property3.id, "c1" + ) + + # ASSERT + self.assertEqual(room_types.id, room_type2.id, "Expected room type not found") + + def test_get_room_type_by_property_diff_company_prop(self): + """ + Room type 1 exists for all companies and for one property. + Room type 2 exists for one company and for all properties. + Room type 3 exists for all companies and for all properties. + Search for property belonging to a different company than + the 2nd room type created. + The method should return 3rd room type. + + PRE: - room type r1 exists + - room_type1 has code c1 + - room_type1 has property pms_property1 + - pms_property1 have the company company1 + - room_type1 has no company + - room type r2 exists + - room_type2 has code c1 + - room_type2 has no properties + - room_type2 has company company1 + - room type room_type3 exists + - room_type3 has code c1 + - room_type3 has no properties + - room_type3 has no company + ACT: - search room type with code c1 and pms_property3 + - pms_property3 have company2 + POST: - room_type3 room type found + """ + # ARRANGE + # room_type1 + self.env["pms.room.type"].create( + { + "name": "Room type 1", + "default_code": "c1", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + "company_id": False, + "class_id": self.room_type_class1.id, + } + ) + # room_type2 + self.env["pms.room.type"].create( + { + "name": "Room type 2", + "default_code": "c1", + "pms_property_ids": False, + "company_id": self.company1.id, + "class_id": self.room_type_class1.id, + } + ) + room_type3 = self.env["pms.room.type"].create( + { + "name": "Room type 3", + "default_code": "c1", + "pms_property_ids": False, + "company_id": False, + "class_id": self.room_type_class1.id, + } + ) + + # ACT + room_types = self.env["pms.room.type"].get_room_types_by_property( + self.pms_property3.id, "c1" + ) + + # ASSERT + self.assertEqual(room_types.id, room_type3.id, "Expected room type not found") + + def test_rooom_type_creation_inconsistency_class(self): + """ + Create a rooom type class belonging to one property. + Create a room type belonging to another property. + Room type creation should fail. + """ + # ARRANGE + room_type_class = self.env["pms.room.type.class"].create( + { + "name": "Room Type Class", + "default_code": "ROOM", + "pms_property_ids": [ + (4, self.pms_property2.id), + ], + }, + ) + # ACT & ASSERT + with self.assertRaises( + UserError, msg="Room Type has been created and it shouldn't" + ): + room_type1 = self.env["pms.room.type"].create( + { + "name": "Room Type", + "default_code": "c1", + "class_id": room_type_class.id, + "pms_property_ids": [ + (4, self.pms_property2.id), + ], + } + ) + room_type1.pms_property_ids = [(4, self.pms_property1.id)] + + def test_rooom_type_creation_consistency_class(self): + """ + Create a rooom type class belonging to one property. + Create a room type belonging to same property. + Room type creation should be successful. + """ + # ARRANGE + room_type_class = self.env["pms.room.type.class"].create( + { + "name": "Room Type Class", + "default_code": "ROOM", + "pms_property_ids": [ + (4, self.pms_property2.id), + ], + }, + ) + # ACT + new_room_type = self.env["pms.room.type"].create( + { + "name": "Room Type", + "default_code": "c1", + "class_id": room_type_class.id, + "pms_property_ids": [ + (4, self.pms_property2.id), + ], + } + ) + # ASSERT + self.assertTrue(new_room_type.id, "Room type creation should be successful.") + + def test_check_board_service_property_integrity(self): + # ARRANGE + room_type = self.env["pms.room.type"].create( + { + "name": "Room Type", + "default_code": "Type1", + "pms_property_ids": self.pms_property1, + "class_id": self.room_type_class1.id, + } + ) + board_service = self.env["pms.board.service"].create( + { + "name": "Board service 1", + "default_code": "c1", + "pms_property_ids": self.pms_property1, + } + ) + # ACT & ASSERT + with self.assertRaises(UserError, msg="Board service created and shouldn't."): + self.env["pms.board.service.room.type"].create( + { + "pms_board_service_id": board_service.id, + "pms_room_type_id": room_type.id, + "pms_property_id": self.pms_property2.id, + } + ) + + def test_check_amenities_property_integrity(self): + self.amenity1 = self.env["pms.amenity"].create( + {"name": "Amenity", "pms_property_ids": self.pms_property1} + ) + # ACT & ASSERT + with self.assertRaises( + UserError, + msg="Shouldn't create room type with amenities belonging to other properties", + ): + self.env["pms.room.type"].create( + { + "name": "Room Type", + "default_code": "Type1", + "class_id": self.room_type_class1.id, + "pms_property_ids": [self.pms_property2.id], + "room_amenity_ids": [self.amenity1.id], + } + ) + + def test_room_type_creation_consistency_amenity(self): + """ + Create an amenity belonging to one property. + Create a room type belonging to same property. + Room type creation should be successful. + """ + # ARRANGE + self.amenity1 = self.env["pms.amenity"].create( + {"name": "Amenity", "pms_property_ids": self.pms_property1} + ) + # ACT + new_room_type = self.env["pms.room.type"].create( + { + "name": "Room Type", + "default_code": "Type1", + "class_id": self.room_type_class1.id, + "pms_property_ids": [self.pms_property1.id], + "room_amenity_ids": [self.amenity1.id], + } + ) + # ASSERT + self.assertTrue(new_room_type.id, "Room type creation should be successful.") diff --git a/pms/tests/test_pms_room_type_class.py b/pms/tests/test_pms_room_type_class.py new file mode 100644 index 0000000000..b41033bd2f --- /dev/null +++ b/pms/tests/test_pms_room_type_class.py @@ -0,0 +1,383 @@ +# Copyright 2021 Eric Antones +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.exceptions import ValidationError + +from .common import TestPms + + +class TestRoomTypeClass(TestPms): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.company2 = cls.env["res.company"].create( + { + "name": "Company 2", + } + ) + cls.pms_property3 = cls.env["pms.property"].create( + { + "name": "Property 3", + "company_id": cls.company2.id, + "default_pricelist_id": cls.pricelist1.id, + } + ) + + # external integrity + def test_external_case_01(self): + """ + Check that a room type class cannot be created with an existing default_code + in the same property. + ---------- + A room type class is created with the default_code = 'c1' in property pms_property1. + Then try to create another room type class in the same property with the same code, + but this should throw a ValidationError. + """ + # ARRANGE + # room_type_class1 + self.env["pms.room.type.class"].create( + { + "name": "Room type class cl1", + "default_code": "c1", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + ) + + # ACT & ASSERT + with self.assertRaises( + ValidationError, msg="The room type class has been created and it shouldn't" + ): + # room_type_class2 + self.env["pms.room.type.class"].create( + { + "name": "Room type class cl2", + "default_code": "c1", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + ) + + def test_external_case_02(self): + """ + Check that a room type class cannot be created with an existing default_code + in the same property. + ---------- + A room type class is created with the default_code = 'c1' in property pms_property1. + Then try to create another room type class with the same code in 3 properties and one + of them is the same property in which the other room type class was + created(pms_property1), but this should throw a ValidationError. + """ + # ARRANGE + self.pms_property2 = self.env["pms.property"].create( + { + "name": "Property 2", + "company_id": self.company1.id, + "default_pricelist_id": self.pricelist1.id, + } + ) + # room_type_class1 + self.env["pms.room.type.class"].create( + { + "name": "Room type class 1", + "default_code": "c1", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + ) + + # ACT & ASSERT + with self.assertRaises( + ValidationError, msg="The room type class has been created and it shouldn't" + ): + # room_type_class2 + self.env["pms.room.type.class"].create( + { + "name": "Room type class cl2", + "default_code": "c1", + "pms_property_ids": [ + ( + 6, + 0, + [ + self.pms_property1.id, + self.pms_property2.id, + self.pms_property3.id, + ], + ) + ], + } + ) + + def test_single_case_01(self): + """ + Check that the room type class was created correctly and that + it is in the property in which it was searched through its default code. + ----------- + Create a room type class with default code as 'c1' for properties 1 and 3 + (different companies), save the value returned by the get_unique_by_property_code() + method, passing property1 and default_code 'c1' as parameters. It is checked + that the id of the room type class created and the id of the record returned by the + method match. + """ + # ARRANGE + room_type_class1 = self.env["pms.room.type.class"].create( + { + "name": "Room type class 1", + "default_code": "c1", + "pms_property_ids": [ + (6, 0, [self.pms_property1.id, self.pms_property3.id]) + ], + } + ) + + # ACT + room_type_classes = self.env["pms.room.type.class"].get_unique_by_property_code( + self.pms_property1.id, "c1" + ) + + # ASSERT + self.assertEqual( + room_type_classes.id, + room_type_class1.id, + "Expected room type class not found", + ) + + def test_single_case_02(self): + """ + Check that the room type class was created correctly and that + it is in the property in which it was searched through its default code. + ----------- + Create a room type class with default code as 'c1' for properties 1 and 3 + (same company), save the value returned by the get_unique_by_property_code() + method, passing property1 and default_code 'c1' as parameters. It is checked + that the id of the room type class created and the id of the record returned by the + method match. + """ + # ARRANGE + self.pms_property2 = self.env["pms.property"].create( + { + "name": "Property 2", + "company_id": self.company1.id, + "default_pricelist_id": self.pricelist1.id, + } + ) + cl1 = self.env["pms.room.type.class"].create( + { + "name": "Room type class 1", + "default_code": "c1", + "pms_property_ids": [ + (6, 0, [self.pms_property1.id, self.pms_property2.id]) + ], + } + ) + + # ACT + room_type_classes = self.env["pms.room.type.class"].get_unique_by_property_code( + self.pms_property1.id, "c1" + ) + + # ASSERT + self.assertEqual( + room_type_classes.id, cl1.id, "Expected room type class not found" + ) + + def test_single_case_03(self): + """ + Check that a room type class created for a property is not + found in another property with a different company. + ----------- + A room type class is created with default_code 'c1' for properties + 1 and 2. It is searched through get_unique_by_property_code() + passing it as parameters 'c1' and property 3 (from a different + company than 1 and 2). It is verified that that room type class + does not exist in that property. + """ + # ARRANGE + self.pms_property2 = self.env["pms.property"].create( + { + "name": "Property 2", + "company_id": self.company1.id, + "default_pricelist_id": self.pricelist1.id, + } + ) + # room_type_class1 + self.env["pms.room.type.class"].create( + { + "name": "Room type class 1", + "default_code": "c1", + "pms_property_ids": [ + (6, 0, [self.pms_property1.id, self.pms_property2.id]) + ], + } + ) + + # ACT + room_type_classes = self.env["pms.room.type.class"].get_unique_by_property_code( + self.pms_property3.id, "c1" + ) + + # ASSERT + self.assertFalse( + room_type_classes, "Room type class found but it should not have found any" + ) + + def test_single_case_04(self): + """ + Check that a room type class with properties = False + (all properties) is found by searching for it in one + of the properties. + -------------- + A room type is created with default code = 'c1' and with + pms_property_ids = False. The room_type_class with + code 'c1' in property 1 is searched through the + get_unique_by_property_code() method and it is verified + that the returned value is correct. + """ + # ARRANGE + room_type_class1 = self.env["pms.room.type.class"].create( + { + "name": "Room type class 1", + "default_code": "c1", + "pms_property_ids": False, + } + ) + + # ACT + room_type_classes = self.env["pms.room.type.class"].get_unique_by_property_code( + self.pms_property1.id, "c1" + ) + + # ASSERT + self.assertEqual( + room_type_classes.id, + room_type_class1.id, + "Expected room type class not found", + ) + + # tests with more than one room type class + def test_multiple_case_01(self): + """ + Check that a room type class can be created with the same + code as another when one of them has pms_property_ids = False + ------------ + A room type class is created with code 'c1' for properties 1 and 3. + Another room type class is created with code 'c1' and the properties + set to False. The room_type with code 'c1' in property 1 is + searched through the get_unique_by_property_code() method and it is + verified that the returned value is correct. + """ + # ARRANGE + room_type_class1 = self.env["pms.room.type.class"].create( + { + "name": "Room type class 1", + "default_code": "c1", + "pms_property_ids": [ + (6, 0, [self.pms_property1.id, self.pms_property3.id]) + ], + } + ) + # room_type_class2 + self.env["pms.room.type.class"].create( + { + "name": "Room type class cl2", + "default_code": "c1", + "pms_property_ids": False, + } + ) + + # ACT + room_type_classes = self.env["pms.room.type.class"].get_unique_by_property_code( + self.pms_property1.id, "c1" + ) + + # ASSERT + self.assertEqual( + room_type_classes.id, + room_type_class1.id, + "Expected room type class not found", + ) + + def test_multiple_case_02(self): + """ + Check that a room type class can be created with the same + code as another when one of them has pms_property_ids = False + ---------- + A room type class is created with code 'c1' for property 1(company1). + Another room type class is created with code 'c1' and the + properties set to False. Then the room_type with code 'c1' + in property 2(company1) is searched through the + get_unique_by_property_code() method and the result is checked. + """ + # ARRANGE + self.pms_property2 = self.env["pms.property"].create( + { + "name": "Property 2", + "company_id": self.company1.id, + "default_pricelist_id": self.pricelist1.id, + } + ) + # room_type_class1 + self.env["pms.room.type.class"].create( + { + "name": "Room type class 1", + "default_code": "c1", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + ) + room_type_class2 = self.env["pms.room.type.class"].create( + { + "name": "Room type class cl2", + "default_code": "c1", + "pms_property_ids": False, + } + ) + + # ACT + room_type_classes = self.env["pms.room.type.class"].get_unique_by_property_code( + self.pms_property2.id, "c1" + ) + + # ASSERT + self.assertEqual( + room_type_classes.id, + room_type_class2.id, + "Expected room type class not found", + ) + + def test_multiple_case_03(self): + """ + Check that a room type class can be created with the same + code as another when one of them has pms_property_ids = False + ---------- + A room type class is created with code 'c1' for property 1(company1). + Another room type class is created with code 'c1' and the + properties set to False. Then the room_type with code 'c1' + in property 3(company2) is searched through the + get_unique_by_property_code() method and the result is checked. + """ + # ARRANGE + # room_type_class1 + self.env["pms.room.type.class"].create( + { + "name": "Room type class cl1", + "default_code": "c1", + "pms_property_ids": [(6, 0, [self.pms_property1.id])], + } + ) + room_type_class2 = self.env["pms.room.type.class"].create( + { + "name": "Room type class cl2", + "default_code": "c1", + "pms_property_ids": False, + } + ) + + # ACT + room_type_classes = self.env["pms.room.type.class"].get_unique_by_property_code( + self.pms_property3.id, "c1" + ) + + # ASSERT + self.assertEqual( + room_type_classes.id, + room_type_class2.id, + "Expected room type class not found", + ) diff --git a/pms/tests/test_pms_sale_channel.py b/pms/tests/test_pms_sale_channel.py new file mode 100644 index 0000000000..fc13ff8132 --- /dev/null +++ b/pms/tests/test_pms_sale_channel.py @@ -0,0 +1,119 @@ +import datetime + +from odoo.exceptions import ValidationError + +from .common import TestPms + + +class TestPmsSaleChannel(TestPms): + def test_reservation_with_invalid_agency(self): + """ + Reservation with an invalid agency cannot be created. + Create a partner that is not an agency and create + a reservation with that partner as an agency. + """ + # ARRANGE + PmsReservation = self.env["pms.reservation"] + not_agency = self.env["res.partner"].create( + {"name": "partner1", "is_agency": False} + ) + # ACT & ASSERT + with self.assertRaises( + ValidationError, msg="Reservation with an invalid agency cannot be created." + ): + PmsReservation.create( + { + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "agency_id": not_agency.id, + "pms_property_id": self.pms_property1.id, + } + ) + + def test_reservation_with_valid_agency(self): + """ + Reservation with a valid agency must be created. + Create a partner that is an agency and create + a reservation with that partner as an agency can be created. + """ + # ARRANGE + PmsReservation = self.env["pms.reservation"] + PmsSaleChannel = self.env["pms.sale.channel"] + sale_channel1 = PmsSaleChannel.create( + {"name": "Test Indirect", "channel_type": "indirect"} + ) + # ACT + agency1 = self.env["res.partner"].create( + { + "name": "partner1", + "is_agency": True, + "sale_channel_id": sale_channel1.id, + } + ) + reservation1 = PmsReservation.create( + { + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "agency_id": agency1.id, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": sale_channel1.id, + } + ) + + # ASSERT + self.assertEqual( + reservation1.agency_id.is_agency, + True, + "Reservation with a valid agency should be created.", + ) + + def test_create_agency_with_sale_channel_indirect(self): + """ + Agency should be created as partner setted as 'agency' + and its sale channel as 'indirect'. + """ + # ARRANGE + PmsSaleChannel = self.env["pms.sale.channel"] + saleChannel1 = PmsSaleChannel.create({"channel_type": "indirect"}) + # ACT + agency1 = self.env["res.partner"].create( + {"name": "example", "is_agency": True, "sale_channel_id": saleChannel1.id} + ) + # ASSERT + self.assertEqual( + agency1.sale_channel_id.channel_type, + "indirect", + "An agency should be an indirect channel.", + ) + + def test_create_agency_with_sale_channel_direct(self): + """ + Agency shouldnt be created as partner setted as 'agency' + and its sale channel as 'direct'. + """ + # ARRANGE + PmsSaleChannel = self.env["pms.sale.channel"] + saleChannel1 = PmsSaleChannel.create({"channel_type": "direct"}) + # ACT & ASSERT + with self.assertRaises( + ValidationError, msg="An agency should be an indirect channel." + ): + self.env["res.partner"].create( + { + "name": "example", + "is_agency": True, + "sale_channel_id": saleChannel1.id, + } + ) + + def test_create_agency_without_sale_channel(self): + """ + Agency creation should fails if there's no sale channel. + """ + # ARRANGE & ACT & ASSERT + with self.assertRaises( + ValidationError, msg="Agency should not be created without sale channel." + ): + self.env["res.partner"].create( + {"name": "example", "is_agency": True, "sale_channel_id": None} + ) diff --git a/pms/tests/test_pms_service.py b/pms/tests/test_pms_service.py new file mode 100644 index 0000000000..b8e710c414 --- /dev/null +++ b/pms/tests/test_pms_service.py @@ -0,0 +1,1058 @@ +import datetime + +from freezegun import freeze_time + +from odoo import fields + +from .common import TestPms + + +class TestPmsService(TestPms): + @classmethod + def setUpClass(cls): + super().setUpClass() + # create room type + cls.room_type_double = cls.env["pms.room.type"].create( + { + "pms_property_ids": [cls.pms_property1.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": cls.room_type_class1.id, + } + ) + # create rooms + cls.room1 = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property1.id, + "name": "Double 101", + "room_type_id": cls.room_type_double.id, + "capacity": 2, + "extra_beds_allowed": 1, + } + ) + + cls.room2 = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property1.id, + "name": "Double 102", + "room_type_id": cls.room_type_double.id, + "capacity": 2, + "extra_beds_allowed": 1, + } + ) + cls.partner1 = cls.env["res.partner"].create( + { + "firstname": "María", + "lastname": "", + "email": "jaime@example.com", + "birthdate_date": "1983-03-01", + "gender": "male", + } + ) + cls.sale_channel_door = cls.env["pms.sale.channel"].create( + {"name": "Door", "channel_type": "direct"} + ) + cls.sale_channel_phone = cls.env["pms.sale.channel"].create( + {"name": "Phone", "channel_type": "direct"} + ) + cls.sale_channel_mail = cls.env["pms.sale.channel"].create( + {"name": "Mail", "channel_type": "direct"} + ) + + @freeze_time("2002-01-01") + def test_reservation_sale_origin_in_board_service(self): + """ + When a reservation is created with board_service, the sale_channel_origin_id + is indicated in reservation. Therefore, the board_service takes + the sale_channel_origin of its reservation + + Reservation --> sale_channel_origin_id = Door + | + --> service.sale_channel_origin_id? It must be Door + """ + # ARRANGE + self.product1 = self.env["product.product"].create( + { + "name": "Test Product 1", + } + ) + + self.board_service1 = self.env["pms.board.service"].create( + { + "name": "Test Board Service 1", + "default_code": "CB1", + "pms_property_ids": [self.pms_property1.id], + } + ) + self.board_service_line1 = self.env["pms.board.service.line"].create( + { + "product_id": self.product1.id, + "pms_board_service_id": self.board_service1.id, + "amount": 10, + "adults": True, + } + ) + + self.board_service_room_type1 = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": self.room_type_double.id, + "pms_board_service_id": self.board_service1.id, + "pms_property_id": self.pms_property1.id, + } + ) + # ACT + self.reservation = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=2), + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "board_service_room_id": self.board_service_room_type1.id, + "sale_channel_origin_id": self.sale_channel_door.id, + "adults": 2, + } + ) + # ASSERT + self.assertEqual( + self.reservation.sale_channel_origin_id, + self.reservation.service_ids.sale_channel_origin_id, + "sale_channel_origin of board_Service must be the same as its reservation", + ) + + @freeze_time("2002-01-11") + def test_change_origin_board_service_not_change_reservation_origin(self): + """ + When you change the sale_channel_origin_id of a board_service in a reservation + that matched the origin of its reservation, if that reservation has reservation_lines + with that sale_channel_id, it doesn't change the origin of reservation + + Reservation --> sale_channel_origin = Door sale_channel_ids = Door + | + --> board_services.sale_channel_origin = Door + + Change board_service origin to Mail + Reservation --> sale_channel_origin = Door sale_channel_ids = {Door, Mail} + | + --> board_services.sale_channel_origin = Mail + + """ + # ARRANGE + self.product1 = self.env["product.product"].create( + { + "name": "Test Product 1", + } + ) + + self.board_service1 = self.env["pms.board.service"].create( + { + "name": "Test Board Service 1", + "default_code": "CB1", + "pms_property_ids": [self.pms_property1.id], + } + ) + self.board_service_line1 = self.env["pms.board.service.line"].create( + { + "product_id": self.product1.id, + "pms_board_service_id": self.board_service1.id, + "amount": 10, + "adults": True, + } + ) + + self.board_service_room_type1 = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": self.room_type_double.id, + "pms_board_service_id": self.board_service1.id, + "pms_property_id": self.pms_property1.id, + } + ) + + self.reservation = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=3), + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "board_service_room_id": self.board_service_room_type1.id, + "sale_channel_origin_id": self.sale_channel_door.id, + } + ) + # ACT + self.reservation.service_ids.sale_channel_origin_id = self.sale_channel_mail.id + # ASSERT + self.assertNotEqual( + self.reservation.sale_channel_origin_id, + self.reservation.service_ids.sale_channel_origin_id, + """sale_channel_origin_id mustn't match + with sale_channel_origin_id of its reservation""", + ) + + @freeze_time("2002-01-17") + def test_change_origin_board_service_in_sale_channels(self): + """ + When sale_channel_origin_id of board_service is changed, the sale_channel_ids + of its reservation and folio are recalculated. Check that these calculations are correct + + Reservation --> sale_channel_origin = Door sale_channel_ids = Door + | + ---> board_service.sale_channel_origin = Door + + Change origin of board services to Phone and + check sale_channel_ids of reservation and folio: + Reservation --> sale_channel_origin = Door sale_channel_ids = {Door, Phone} + | + ---> board_service.sale_channel_origin = Phone + + Reservation.folio_id.sale_channel_ids = {Door, Phone} + """ + # ARRANGE + self.product1 = self.env["product.product"].create( + { + "name": "Test Product 1", + } + ) + + self.board_service1 = self.env["pms.board.service"].create( + { + "name": "Test Board Service 1", + "default_code": "CB1", + "pms_property_ids": [self.pms_property1.id], + } + ) + self.board_service_line1 = self.env["pms.board.service.line"].create( + { + "product_id": self.product1.id, + "pms_board_service_id": self.board_service1.id, + "amount": 10, + "adults": True, + } + ) + + self.board_service_room_type1 = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": self.room_type_double.id, + "pms_board_service_id": self.board_service1.id, + "pms_property_id": self.pms_property1.id, + } + ) + self.reservation = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=3), + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "board_service_room_id": self.board_service_room_type1.id, + "sale_channel_origin_id": self.sale_channel_door.id, + "adults": 2, + } + ) + # ACT + self.reservation.service_ids.sale_channel_origin_id = self.sale_channel_phone + + sale_channel_ids = [ + self.reservation.folio_id.sale_channel_ids, + self.reservation.sale_channel_ids, + ] + + expected_sale_channel_ids = [ + self.sale_channel_door, + self.sale_channel_phone, + ] + # ASSERT + for sale_channel in sale_channel_ids: + with self.subTest(k=sale_channel): + self.assertItemsEqual( + sale_channel, + expected_sale_channel_ids, + "sale_channel_ids must contain sale_channel_origin_id of all board_service", + ) + + @freeze_time("2002-01-19") + def test_change_origin_reservation_change_origin_services(self): + """ + When sale_channel_origin_id of reservation is changed, + sale_channel_origin_id of its services having the same origin + must also be changed + + Reservation ---> sale_channel_origin = Door + | + --> service.sale_channel_origin = Door + + Change sale_channel_origin to Mail, expected results: + Reservation ---> sale_channel_origin = Mail + | + --> service.sale_channel_origin = Mail ----CHECKING THIS--- + + """ + # ARRANGE + self.product1 = self.env["product.product"].create( + { + "name": "Test Product 1", + } + ) + + self.board_service1 = self.env["pms.board.service"].create( + { + "name": "Test Board Service 1", + "default_code": "CB1", + "pms_property_ids": [self.pms_property1.id], + } + ) + self.board_service_line1 = self.env["pms.board.service.line"].create( + { + "product_id": self.product1.id, + "pms_board_service_id": self.board_service1.id, + "amount": 10, + "adults": True, + } + ) + + self.board_service_room_type1 = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": self.room_type_double.id, + "pms_board_service_id": self.board_service1.id, + "pms_property_id": self.pms_property1.id, + } + ) + + self.reservation = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=3), + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "board_service_room_id": self.board_service_room_type1.id, + "sale_channel_origin_id": self.sale_channel_door.id, + "adults": 2, + } + ) + # ACT + self.reservation.sale_channel_origin_id = self.sale_channel_mail + + # ASSERT + self.assertIn( + self.reservation.sale_channel_origin_id, + self.reservation.service_ids.sale_channel_origin_id, + "sale_channel_origin_id of service must be the same that its reservation ", + ) + + @freeze_time("2002-02-01") + def test_reservation_sale_origin_in_service(self): + """ + When a reservation is created with service, the sale_channel_origin_id + is indicated in reservation. Therefore, the service takes + the sale_channel_origin of its reservation + + Reservation --> sale_channel_origin_id = Door + | + --> service.sale_channel_origin_id? It must be Door + """ + # ARRANGE + self.product1 = self.env["product.product"].create( + { + "name": "Test Product 1", + "per_day": True, + "consumed_on": "after", + "is_extra_bed": True, + } + ) + + self.reservation = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=2), + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "sale_channel_origin_id": self.sale_channel_door.id, + } + ) + # ACT + self.service1 = self.env["pms.service"].create( + { + "reservation_id": self.reservation.id, + "product_id": self.product1.id, + "is_board_service": False, + } + ) + # ASSERT + self.assertEqual( + self.reservation.sale_channel_origin_id, + self.service1.sale_channel_origin_id, + "sale_channel_origin of service must be the same as its reservation", + ) + + @freeze_time("2002-02-03") + def test_origin_different_in_services_check_sale_channel_ids(self): + """ + Check that sale_channel_ids is calculated well (in folio and + reservation) when a reservation has services from different sale_channels + + Reservation --> sale_channel_origin = Door sale_channel_ids = Door + | + --> service.sale_channel_origin = Door + + Add in reservation another service with sale_channel_origin = Phone, expected results: + + Reservation --> sale_channel_origin = Door sale_channel_ids = Door, Phone + | + --> service[0].sale_channel_origin = Door + | + --> service[1].sale_channel_origin = Phone + + Reservation.folio_id.sale_channels = {Door, Phone} + + Check sale_channel_ids of reservation and its folio + """ + # ARRANGE + self.product1 = self.env["product.product"].create( + { + "name": "Test Product 1", + "per_day": True, + "consumed_on": "after", + "is_extra_bed": True, + } + ) + + self.reservation = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=2), + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "sale_channel_origin_id": self.sale_channel_door.id, + } + ) + # ACT + self.service1 = self.env["pms.service"].create( + { + "reservation_id": self.reservation.id, + "product_id": self.product1.id, + "is_board_service": False, + } + ) + self.service2 = self.env["pms.service"].create( + { + "reservation_id": self.reservation.id, + "product_id": self.product1.id, + "is_board_service": False, + "sale_channel_origin_id": self.sale_channel_phone.id, + } + ) + + sale_channel_ids = [ + self.reservation.folio_id.sale_channel_ids, + self.reservation.sale_channel_ids, + ] + + expected_sale_channel_ids = self.reservation.service_ids.mapped( + "sale_channel_origin_id" + ) + + # ASSERT + for sale_channel in sale_channel_ids: + with self.subTest(k=sale_channel): + self.assertItemsEqual( + sale_channel, + expected_sale_channel_ids, + "sale_channel_ids must contain sale_channel_id of all board_service_lines", + ) + + @freeze_time("2002-02-16") + def test_change_origin_service_not_change_reservation_origin(self): + """ + When you change the sale_channel_origin_id of a service in a reservation + that matched the origin of its reservation, if that reservation has reservation_lines + with that sale_channel_id, it doesn't change the origin of reservation + + Reservation --> sale_channel_origin = Door + | + --> service.sale_channel_origin = Door + + Change sale_channel_origin of service to Phone, expected results: + + Reservation --> sale_channel_origin = Door ----CHECKING THIS--- + | + --> service.sale_channel_origin = Phone + + """ + # ARRANGE + self.product1 = self.env["product.product"].create( + { + "name": "Test Product 1", + "per_day": True, + "consumed_on": "after", + "is_extra_bed": True, + } + ) + + self.reservation = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=2), + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "sale_channel_origin_id": self.sale_channel_door.id, + } + ) + + self.service1 = self.env["pms.service"].create( + { + "reservation_id": self.reservation.id, + "product_id": self.product1.id, + "is_board_service": False, + } + ) + # ACT + self.reservation.service_ids.sale_channel_origin_id = self.sale_channel_phone.id + # ASSERT + self.assertNotEqual( + self.reservation.sale_channel_origin_id, + self.reservation.service_ids.sale_channel_origin_id, + """sale_channel_origin_id mustn't match + with sale_channel_origin_id of its reservation""", + ) + + @freeze_time("2002-02-23") + def test_change_origin_in_services_check_sale_channel_ids(self): + """ + Check that sale_channel_ids is calculated well (in folio and + reservation) when a service of a reservation change its sale_channel_origin + + Reservation --> sale_channel_origin = Door sale_channel_ids = Door + | + --> service.sale_channel_origin = Door + + Change sale_channel_origin of service to Mail, expected results: + + Reservation --> sale_channel_origin = Door + --> sale_channel_ids = Door, Mail -----CHECKING THIS---- + | + --> service.sale_channel_origin = Mail + + Reservation.folio_id.sale_channels = {Door, Mail} -----CHECKING THIS---- + + Check sale_channel_ids of reservation and its folio + """ + # ARRANGE + self.product1 = self.env["product.product"].create( + { + "name": "Test Product 1", + "per_day": True, + "consumed_on": "after", + "is_extra_bed": True, + } + ) + + self.reservation = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=2), + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "sale_channel_origin_id": self.sale_channel_door.id, + } + ) + + self.service1 = self.env["pms.service"].create( + { + "reservation_id": self.reservation.id, + "product_id": self.product1.id, + "is_board_service": False, + } + ) + + # ACT + self.service1.sale_channel_origin_id = self.sale_channel_mail + + sale_channel_ids = [ + self.reservation.folio_id.sale_channel_ids.ids, + self.reservation.sale_channel_ids.ids, + ] + + expected_sale_channel_ids = [ + self.sale_channel_door.id, + self.sale_channel_mail.id, + ] + + # ASSERT + for sale_channel in sale_channel_ids: + with self.subTest(k=sale_channel): + self.assertItemsEqual( + sale_channel, + expected_sale_channel_ids, + "sale_channel_ids must contain sale_channel_origin_id of all services", + ) + + @freeze_time("2002-02-25") + def test_change_origin_in_reservation_change_origin_service(self): + """ + Check that when change sale_channel_origin of a reservation, sale_channel_origin + of services that match with the origin changed, change too + + Service --> sale_channel_origin_id = Door sale_channel_ids = {Door, Phone} + | + --> service[0].sale_channel_id = Door + | + --> service[1].sale_channel_id = Phone + + Change service origin to mail, expected results: + Reservation --> sale_channel_origin_id = Mail sale_channel_ids = {Mail, Phone} + | + --> service[0].sale_channel_id = Mail -----------CHECKING THIS--- + | + --> service[1].sale_channel_id = Phone + """ + # ARRANGE + self.product1 = self.env["product.product"].create( + { + "name": "Test Product 1", + "per_day": True, + "consumed_on": "after", + "is_extra_bed": True, + } + ) + + self.reservation = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=2), + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "sale_channel_origin_id": self.sale_channel_door.id, + } + ) + + self.service1 = self.env["pms.service"].create( + { + "reservation_id": self.reservation.id, + "product_id": self.product1.id, + "is_board_service": False, + } + ) + self.service2 = self.env["pms.service"].create( + { + "reservation_id": self.reservation.id, + "product_id": self.product1.id, + "is_board_service": False, + "sale_channel_origin_id": self.sale_channel_phone.id, + } + ) + + # ACT + self.reservation.sale_channel_origin_id = self.sale_channel_mail + + # ASSERT + self.assertIn( + self.reservation.sale_channel_origin_id, + self.reservation.service_ids.mapped("sale_channel_origin_id"), + "sale_channel_origin_id of that service should be changed", + ) + + @freeze_time("2002-03-29") + def test_change_origin_in_reservation_no_change_origin_service(self): + """ + Check that when change sale_channel_origin of a reservation, sale_channel_origin + of services that don't match with the origin changed don't change + + Service --> sale_channel_origin_id = Door sale_channel_ids = {Door, Phone} + | + --> service[0].sale_channel_id = Door + | + --> service[1].sale_channel_id = Phone + + Change service origin to mail, expected results: + Reservation --> sale_channel_origin_id = Mail sale_channel_ids = {Mail, Phone} + | + --> service[0].sale_channel_id = Mail + | + --> service[1].sale_channel_id = Phone -----------CHECKING THIS--- + """ + # ARRANGE + self.product1 = self.env["product.product"].create( + { + "name": "Test Product 1", + "per_day": True, + "consumed_on": "after", + "is_extra_bed": True, + } + ) + + self.reservation = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=2), + "room_type_id": self.room_type_double.id, + "partner_id": self.partner1.id, + "pms_property_id": self.pms_property1.id, + "pricelist_id": self.pricelist1.id, + "sale_channel_origin_id": self.sale_channel_door.id, + } + ) + + self.service1 = self.env["pms.service"].create( + { + "reservation_id": self.reservation.id, + "product_id": self.product1.id, + "is_board_service": False, + } + ) + self.service2 = self.env["pms.service"].create( + { + "reservation_id": self.reservation.id, + "product_id": self.product1.id, + "is_board_service": False, + "sale_channel_origin_id": self.sale_channel_phone.id, + } + ) + + # ACT + self.reservation.sale_channel_origin_id = self.sale_channel_mail + + # ASSERT + self.assertIn( + self.sale_channel_phone, + self.reservation.service_ids.mapped("sale_channel_origin_id"), + "sale_channel_origin_id of that service shouldn't be changed", + ) + + @freeze_time("2002-03-01") + def test_new_service_in_folio_sale_channel_origin(self): + """ + Check that when a service is created within a folio already created, + this service will use the sale_channel_origin_id of the folio as + its sale_channel_origin_id + + Folio ----> sale_channel_origin_id = Door + | + ----> service.sale_channel_origin_id? It must be Door + """ + # ARRANGE + self.product1 = self.env["product.product"].create( + { + "name": "Test Product 1", + "per_day": True, + "consumed_on": "after", + "is_extra_bed": True, + } + ) + self.folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_name": self.partner1.name, + "sale_channel_origin_id": self.sale_channel_door.id, + } + ) + # ACT + self.service1 = self.env["pms.service"].create( + { + "product_id": self.product1.id, + "is_board_service": False, + "folio_id": self.folio1.id, + } + ) + # ASSERT + self.assertEqual( + self.folio1.sale_channel_origin_id, + self.service1.sale_channel_origin_id, + "Service that is just created must have its folio sale_channel_origin", + ) + + @freeze_time("2002-03-03") + def test_change_origin_folio_change_origin_one_service(self): + """ + Check that when a folio has a service, changing the sale_channel_origin + of folio changes sale_channel_origin of it service + + Folio ----> sale_channel_origin_id = Door + | + ----> service.sale_channel_origin_id = Door + + Change sale_channel_origin of folio to Mail + Folio ----> sale_channel_origin_id = Mail + | + ----> service.sale_channel_origin_id = Mail ---CHECKING THIS--- + """ + # ARRANGE + self.product1 = self.env["product.product"].create( + { + "name": "Test Product 1", + "per_day": True, + "consumed_on": "after", + "is_extra_bed": True, + } + ) + self.folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_name": self.partner1.name, + "sale_channel_origin_id": self.sale_channel_door.id, + } + ) + + self.service1 = self.env["pms.service"].create( + { + "product_id": self.product1.id, + "is_board_service": False, + "folio_id": self.folio1.id, + } + ) + # ACT + self.folio1.sale_channel_origin_id = self.sale_channel_mail.id + + # ASSERT + self.assertEqual( + self.folio1.sale_channel_origin_id, + self.service1.sale_channel_origin_id, + "Service must have equal sale_channel_origin than folio", + ) + + @freeze_time("2002-03-05") + def test_change_origin_service_change_origin_folio(self): + """ + When a folio has only one service, when changing the service sale_channel_origin + folio.sale_channel_origin will also change + + Folio ----> sale_channel_origin_id = Door + | + ----> service.sale_channel_origin_id = Door + + Change sale_channel_origin of service to Mail + Folio ----> sale_channel_origin_id = Mail ---CHECKING THIS--- + | + ----> service.sale_channel_origin_id = Mail + """ + # ARRANGE + self.product1 = self.env["product.product"].create( + { + "name": "Test Product 1", + "per_day": True, + "consumed_on": "after", + "is_extra_bed": True, + } + ) + self.folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_name": self.partner1.name, + "sale_channel_origin_id": self.sale_channel_door.id, + } + ) + + self.service1 = self.env["pms.service"].create( + { + "product_id": self.product1.id, + "is_board_service": False, + "folio_id": self.folio1.id, + } + ) + # ACT + self.service1.sale_channel_origin_id = self.sale_channel_mail.id + + # ASSERT + self.assertEqual( + self.folio1.sale_channel_origin_id, + self.service1.sale_channel_origin_id, + "Service must have equal sale_channel_origin than folio", + ) + + @freeze_time("2002-03-07") + def test_folio_sale_channels_with_service_different_origins(self): + """ + Check that on a folio with services with differents sale_channel_origin + the sale_channel_ids of folio are calculated well. + In this case sale_channel_ids must be formed by sale_channel_origin of its + services + + Folio ----> sale_channel_origin_id = Door + ----> sale_cahnnel_ids = {Door, Mail} ---CHECKING THIS---- + | + ----> service.sale_channel_origin_id = Door + | + ----> service.sale_channel_origin_id = Mail + """ + # ARRANGE + self.product1 = self.env["product.product"].create( + { + "name": "Test Product 1", + "per_day": True, + "consumed_on": "after", + "is_extra_bed": True, + } + ) + self.folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_name": self.partner1.name, + "sale_channel_origin_id": self.sale_channel_door.id, + } + ) + # ACT + self.service1 = self.env["pms.service"].create( + { + "product_id": self.product1.id, + "is_board_service": False, + "folio_id": self.folio1.id, + } + ) + self.service2 = self.env["pms.service"].create( + { + "product_id": self.product1.id, + "is_board_service": False, + "folio_id": self.folio1.id, + "sale_channel_origin_id": self.sale_channel_mail.id, + } + ) + + expected_sale_channels = self.folio1.service_ids.mapped( + "sale_channel_origin_id" + ) + + # ASSERT + self.assertEqual( + self.folio1.sale_channel_ids, + expected_sale_channels, + "sale_channel_ids must be the set of sale_channel_origin of its services", + ) + + @freeze_time("2002-03-10") + def test_change_origin_folio_change_origin_service(self): + """ + Check that when a folio has several services with different sale_channel_origin_id + and change sale_channel_origin_id of folio, only changes origin of those services that + match with the sale_channel_origin changed + + Folio ----> sale_channel_origin_id = Door + | + ----> service[0].sale_channel_origin_id = Door + | + ----> service[1].sale_channel_origin_id = Mail + + Change origin of folio to Phone, expected results: + Folio ----> sale_channel_origin_id = Phone + | + ----> service[0].sale_channel_origin_id = Phone ---CHECKIN THIS--- + | + ----> service[1].sale_channel_origin_id = Mail + """ + # ARRANGE + self.product1 = self.env["product.product"].create( + { + "name": "Test Product 1", + "per_day": True, + "consumed_on": "after", + "is_extra_bed": True, + } + ) + self.folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_name": self.partner1.name, + "sale_channel_origin_id": self.sale_channel_door.id, + } + ) + + self.service1 = self.env["pms.service"].create( + { + "product_id": self.product1.id, + "is_board_service": False, + "folio_id": self.folio1.id, + } + ) + self.service2 = self.env["pms.service"].create( + { + "product_id": self.product1.id, + "is_board_service": False, + "folio_id": self.folio1.id, + "sale_channel_origin_id": self.sale_channel_mail.id, + } + ) + # ACT + self.folio1.sale_channel_origin_id = self.sale_channel_phone.id + + # ASSERT + self.assertIn( + self.sale_channel_phone, + self.folio1.service_ids.mapped("sale_channel_origin_id"), + "sale_channel_origin_id of that service must be changed", + ) + + @freeze_time("2002-03-13") + def test_change_origin_folio_no_change_origin_service(self): + """ + Check that when a folio has several services with different sale_channel_origin_id + and change sale_channel_origin_id of folio, only changes origin of those services that + match with the sale_channel_origin changed. Then services that didn't initially + match with sale_channel_origin of folio shouldn't have changed + + Folio ----> sale_channel_origin_id = Door + | + ----> service[0].sale_channel_origin_id = Door + | + ----> service[1].sale_channel_origin_id = Mail + + Change origin of folio to Phone, expected results: + Folio ----> sale_channel_origin_id = Phone + | + ----> service[0].sale_channel_origin_id = Phone + | + ----> service[1].sale_channel_origin_id = Mail ---CHECKIN THIS--- + """ + # ARRANGE + self.product1 = self.env["product.product"].create( + { + "name": "Test Product 1", + "per_day": True, + "consumed_on": "after", + "is_extra_bed": True, + } + ) + self.folio1 = self.env["pms.folio"].create( + { + "pms_property_id": self.pms_property1.id, + "partner_name": self.partner1.name, + "sale_channel_origin_id": self.sale_channel_door.id, + } + ) + + self.service1 = self.env["pms.service"].create( + { + "product_id": self.product1.id, + "is_board_service": False, + "folio_id": self.folio1.id, + } + ) + self.service2 = self.env["pms.service"].create( + { + "product_id": self.product1.id, + "is_board_service": False, + "folio_id": self.folio1.id, + "sale_channel_origin_id": self.sale_channel_mail.id, + } + ) + # ACT + self.folio1.sale_channel_origin_id = self.sale_channel_phone.id + + # ASSERT + self.assertIn( + self.sale_channel_mail, + self.folio1.service_ids.mapped("sale_channel_origin_id"), + "sale_channel_origin_id of that service mustn't be changed", + ) diff --git a/pms/tests/test_pms_simple_invoice.py b/pms/tests/test_pms_simple_invoice.py new file mode 100644 index 0000000000..d4e1dd466b --- /dev/null +++ b/pms/tests/test_pms_simple_invoice.py @@ -0,0 +1,11 @@ +from freezegun import freeze_time + +from odoo.tests.common import SavepointCase + +freeze_time("2000-02-02") + + +class TestPmsInvoiceSimpleInvoice(SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() diff --git a/pms/tests/test_pms_wizard_massive_changes.py b/pms/tests/test_pms_wizard_massive_changes.py new file mode 100644 index 0000000000..5b8d930b87 --- /dev/null +++ b/pms/tests/test_pms_wizard_massive_changes.py @@ -0,0 +1,1315 @@ +import datetime + +from freezegun import freeze_time + +from odoo import fields + +from .common import TestPms + + +class TestPmsWizardMassiveChanges(TestPms): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.availability_plan1 = cls.env["pms.availability.plan"].create( + { + "name": "Availability plan for TEST", + "pms_pricelist_ids": [(6, 0, [cls.pricelist1.id])], + } + ) + + # MASSIVE CHANGE WIZARD TESTS ON AVAILABILITY RULES + + def test_num_availability_rules_create(self): + """ + Rules should be created consistently for 1,2,3,4 days + subtests: {1 day -> 1 rule, n days -> n rules} + """ + # ARRANGE + room_type_double = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": self.room_type_class1.id, + } + ) + for days in [0, 1, 2, 3]: + with self.subTest(k=days): + num_exp_rules_to_create = days + 1 + # ACT + self.env["pms.massive.changes.wizard"].create( + { + "massive_changes_on": "availability_plan", + "availability_plan_ids": [(6, 0, [self.availability_plan1.id])], + "start_date": fields.date.today(), + "end_date": fields.date.today() + datetime.timedelta(days=days), + "room_type_ids": [(6, 0, [room_type_double.id])], + "pms_property_ids": [self.pms_property1.id], + } + ).apply_massive_changes() + # ASSERT + self.assertEqual( + len(self.availability_plan1.rule_ids), + num_exp_rules_to_create, + "the number of rules created should contains all the " + "days between start and finish (both included)", + ) + + def test_num_availability_rules_create_no_room_type(self): + """ + Rules should be created consistently for all rooms & days. + (days * num rooom types) + Create rules for 4 days and for all room types. + """ + # ARRANGE + date_from = fields.date.today() + date_to = fields.date.today() + datetime.timedelta(days=3) + + num_room_types = self.env["pms.room.type"].search_count( + [ + "|", + ("pms_property_ids", "=", False), + ("pms_property_ids", "in", self.pms_property1.id), + ] + ) + num_exp_rules_to_create = ((date_to - date_from).days + 1) * num_room_types + + # ACT + self.env["pms.massive.changes.wizard"].create( + { + "massive_changes_on": "availability_plan", + "availability_plan_ids": [(6, 0, [self.availability_plan1.id])], + "start_date": date_from, + "end_date": date_to, + "pms_property_ids": [self.pms_property1.id], + } + ).apply_massive_changes() + + # ASSERT + self.assertEqual( + len(self.availability_plan1.rule_ids), + num_exp_rules_to_create, + "the number of rules created by the wizard should consider all " + "room types", + ) + + def test_value_availability_rules_create(self): + """ + The value of the rules created is setted properly. + """ + # ARRANGE + room_type_double = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": self.room_type_class1.id, + } + ) + date_from = fields.date.today() + date_to = fields.date.today() + vals = { + "massive_changes_on": "availability_plan", + "availability_plan_ids": [(6, 0, [self.availability_plan1.id])], + "start_date": date_from, + "end_date": date_to, + "room_type_ids": [(6, 0, [room_type_double.id])], + "quota": 50, + "max_avail": 5, + "min_stay": 10, + "min_stay_arrival": 10, + "max_stay": 10, + "max_stay_arrival": 10, + "closed": True, + "closed_arrival": True, + "closed_departure": True, + "pms_property_ids": [self.pms_property1.id], + } + # ACT + self.env["pms.massive.changes.wizard"].create(vals).apply_massive_changes() + # ASSERT + del vals["massive_changes_on"] + del vals["availability_plan_ids"] + del vals["start_date"] + del vals["end_date"] + del vals["room_type_ids"] + del vals["pms_property_ids"] + for key in vals: + with self.subTest(k=key): + self.assertEqual( + self.availability_plan1.rule_ids[0][key], + vals[key], + "The value of " + key + " is not correctly established", + ) + + @freeze_time("1980-12-01") + def test_day_of_week_availability_rules_create(self): + """ + Rules for each day of week should be created. + """ + # ARRANGE + room_type_double = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": self.room_type_class1.id, + } + ) + test_case_week_days = [ + [1, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 0, 0, 1], + ] + date_from = fields.date.today() + date_to = fields.date.today() + datetime.timedelta(days=6) + + wizard = self.env["pms.massive.changes.wizard"].create( + { + "massive_changes_on": "availability_plan", + "availability_plan_ids": [(6, 0, [self.availability_plan1.id])], + "room_type_ids": [(6, 0, [room_type_double.id])], + "start_date": date_from, + "end_date": date_to, + "pms_property_ids": [self.pms_property1.id], + } + ) + + for index, test_case in enumerate(test_case_week_days): + with self.subTest(k=test_case): + # ARRANGE + wizard.write( + { + "apply_on_monday": test_case[0], + "apply_on_tuesday": test_case[1], + "apply_on_wednesday": test_case[2], + "apply_on_thursday": test_case[3], + "apply_on_friday": test_case[4], + "apply_on_saturday": test_case[5], + "apply_on_sunday": test_case[6], + } + ) + # ACT + wizard.apply_massive_changes() + availability_rules = self.availability_plan1.rule_ids.sorted( + key=lambda s: s.date + ) + # ASSERT + self.assertTrue( + availability_rules[index].date.timetuple()[6] == index + and test_case[index], + "Rule not created on correct day of week.", + ) + + def test_no_overwrite_values_not_setted(self): + """ + A rule value shouldnt overwrite with the default values + another rules for the same day and room type. + Create a rule with quota and another rule for the same date with max + avail. Should not overwrite quota. + """ + # ARRANGE + room_type_double = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": self.room_type_class1.id, + } + ) + date = fields.date.today() + initial_quota = 20 + self.env["pms.availability.plan.rule"].create( + { + "availability_plan_id": self.availability_plan1.id, + "room_type_id": room_type_double.id, + "date": date, + "quota": initial_quota, + "pms_property_id": self.pms_property1.id, + } + ) + vals_wizard = { + "massive_changes_on": "availability_plan", + "availability_plan_ids": [(6, 0, [self.availability_plan1.id])], + "start_date": date, + "end_date": date, + "room_type_ids": [(6, 0, [room_type_double.id])], + "apply_max_avail": True, + "max_avail": 2, + "pms_property_ids": [self.pms_property1.id], + } + # ACT + self.env["pms.massive.changes.wizard"].create( + vals_wizard + ).apply_massive_changes() + # ASSERT + self.assertEqual( + self.availability_plan1.rule_ids[0].quota, + initial_quota, + "A rule value shouldnt overwrite with the default values " + "another rules for the same day and room type", + ) + + def test_several_availability_plans(self): + """ + If several availability plans are set, the wizard should create as + many rules as availability plans. + """ + # ARRANGE + room_type_double = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": self.room_type_class1.id, + } + ) + availability_plan2 = self.env["pms.availability.plan"].create( + { + "name": "Second availability plan for TEST", + "pms_pricelist_ids": [self.pricelist1.id], + } + ) + expected_av_plans = [ + self.availability_plan1.id, + availability_plan2.id, + ] + date_from = fields.date.today() + date_to = fields.date.today() + vals_wizard = { + "massive_changes_on": "availability_plan", + "availability_plan_ids": [ + ( + 6, + 0, + [ + self.availability_plan1.id, + availability_plan2.id, + ], + ) + ], + "room_type_ids": [(6, 0, [room_type_double.id])], + "pms_property_ids": [self.pms_property1.id], + "start_date": date_from, + "end_date": date_to, + } + # ACT + self.env["pms.massive.changes.wizard"].create( + vals_wizard + ).apply_massive_changes() + # ASSERT + self.assertEqual( + set(expected_av_plans), + set( + self.env["pms.availability.plan.rule"] + .search([("room_type_id", "=", room_type_double.id)]) + .mapped("availability_plan_id") + .ids + ), + "The wizard should create as many rules as availability plans given.", + ) + + def test_several_room_types_availability_plan(self): + """ + If several room types are set, the wizard should create as + many rules as room types. + """ + # ARRANGE + room_type_double = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": self.room_type_class1.id, + } + ) + room_type_single = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id], + "name": "Single Test", + "default_code": "SNG_Test", + "class_id": self.room_type_class1.id, + } + ) + expected_room_types = [ + room_type_double.id, + room_type_single.id, + ] + date_from = fields.date.today() + date_to = fields.date.today() + vals_wizard = { + "massive_changes_on": "availability_plan", + "availability_plan_ids": [(6, 0, [self.availability_plan1.id])], + "room_type_ids": [ + ( + 6, + 0, + [room_type_double.id, room_type_single.id], + ) + ], + "pms_property_ids": [self.pms_property1.id], + "start_date": date_from, + "end_date": date_to, + } + # ACT + self.env["pms.massive.changes.wizard"].create( + vals_wizard + ).apply_massive_changes() + # ASSERT + self.assertEqual( + set(expected_room_types), + set( + self.env["pms.availability.plan.rule"] + .search([("availability_plan_id", "=", self.availability_plan1.id)]) + .mapped("room_type_id") + .ids + ), + "The wizard should create as many rules as room types given.", + ) + + def test_several_properties_availability_plan(self): + """ + If several properties are set, the wizard should create as + many rules as properties. + """ + # ARRANGE + pms_property2 = self.env["pms.property"].create( + { + "name": "MY 2nd PMS TEST", + "company_id": self.env.ref("base.main_company").id, + } + ) + room_type_double = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": self.room_type_class1.id, + } + ) + room_type_double.pms_property_ids = [ + (6, 0, [self.pms_property1.id, pms_property2.id]) + ] + expected_properties = [ + self.pms_property1.id, + pms_property2.id, + ] + date_from = fields.date.today() + date_to = fields.date.today() + vals_wizard = { + "massive_changes_on": "availability_plan", + "availability_plan_ids": [(6, 0, [self.availability_plan1.id])], + "room_type_ids": [(6, 0, [room_type_double.id])], + "pms_property_ids": [(6, 0, [self.pms_property1.id, pms_property2.id])], + "start_date": date_from, + "end_date": date_to, + } + # ACT + self.env["pms.massive.changes.wizard"].create( + vals_wizard + ).apply_massive_changes() + # ASSERT + self.assertEqual( + set(expected_properties), + set( + self.env["pms.availability.plan.rule"] + .search([("availability_plan_id", "=", self.availability_plan1.id)]) + .mapped("pms_property_id") + .ids + ), + "The wizard should create as many rules as properties given.", + ) + + def test_create_rule_existing_previous(self): + """ + If there's a previous rule with some value and new values are set + that contains date of previuos value should overwrite the value. + """ + # ARRANGE + room_type_double = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": self.room_type_class1.id, + } + ) + date = fields.date.today() + initial_quota = 20 + self.env["pms.availability.plan.rule"].create( + { + "availability_plan_id": self.availability_plan1.id, + "room_type_id": room_type_double.id, + "date": date, + "quota": initial_quota, + "pms_property_id": self.pms_property1.id, + } + ) + vals_wizard = { + "massive_changes_on": "availability_plan", + "availability_plan_ids": [(6, 0, [self.availability_plan1.id])], + "start_date": date, + "end_date": fields.date.today() + datetime.timedelta(days=1), + "room_type_ids": [(6, 0, [room_type_double.id])], + "apply_quota": True, + "quota": 20, + "pms_property_ids": [self.pms_property1.id], + } + + # ACT + self.env["pms.massive.changes.wizard"].create( + vals_wizard + ).apply_massive_changes() + + # ASSERT + self.assertEqual( + self.availability_plan1.rule_ids[0].quota, + initial_quota, + "A rule value shouldnt overwrite with the default values " + "another rules for the same day and room type", + ) + + # MASSIVE CHANGE WIZARD TESTS ON PRICELIST ITEMS + + def test_pricelist_items_create(self): + """ + Pricelist items should be created consistently for 1,2,3,4 days + subtests: {1 day -> 1 pricelist item, n days -> n pricelist items} + """ + # ARRANGE + room_type_double = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": self.room_type_class1.id, + } + ) + for days in [0, 1, 2, 3]: + with self.subTest(k=days): + # ARRANGE + num_exp_items_to_create = days + 1 + self.pricelist1.item_ids = False + # ACT + self.env["pms.massive.changes.wizard"].create( + { + "massive_changes_on": "pricelist", + "pricelist_ids": [(6, 0, [self.pricelist1.id])], + "start_date": fields.date.today(), + "end_date": fields.date.today() + datetime.timedelta(days=days), + "room_type_ids": [(6, 0, [room_type_double.id])], + "pms_property_ids": [self.pms_property1.id], + "price": 20, + } + ).apply_massive_changes() + # ASSERT + self.assertEqual( + len(self.pricelist1.item_ids if self.pricelist1.item_ids else []), + num_exp_items_to_create, + "the number of rules created by the wizard should include all the " + "days between start and finish (both included)", + ) + + def test_num_pricelist_items_create_no_room_type(self): + """ + Pricelist items should be created consistently for all rooms & days. + (days * num rooom types) + Create pricelist item for 4 days and for all room types. + """ + # ARRANGE + date_from = fields.date.today() + date_to = fields.date.today() + datetime.timedelta(days=3) + num_room_types = self.env["pms.room.type"].search_count( + [ + "|", + ("pms_property_ids", "=", False), + ("pms_property_ids", "in", self.pms_property1.id), + ] + ) + num_exp_items_to_create = ((date_to - date_from).days + 1) * num_room_types + # ACT + self.env["pms.massive.changes.wizard"].create( + { + "massive_changes_on": "pricelist", + "pricelist_ids": [(6, 0, [self.pricelist1.id])], + "start_date": date_from, + "end_date": date_to, + "pms_property_ids": [self.pms_property1.id], + "price": 20, + } + ).apply_massive_changes() + # ASSERT + self.assertEqual( + len(self.pricelist1.item_ids), + num_exp_items_to_create, + "the number of rules created by the wizard should consider all " + "room types when one is not applied", + ) + + def test_value_pricelist_items_create(self): + """ + The value of the rules created is setted properly. + """ + # ARRANGE + room_type_double = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": self.room_type_class1.id, + } + ) + date_from = fields.date.today() + date_to = fields.date.today() + price = 20 + min_quantity = 3 + vals = { + "pricelist_id": self.pricelist1, + "date_start": date_from, + "date_end": date_to, + "compute_price": "fixed", + "applied_on": "0_product_variant", + "product_id": room_type_double.product_id, + "fixed_price": price, + "min_quantity": min_quantity, + } + # ACT + self.env["pms.massive.changes.wizard"].create( + { + "massive_changes_on": "pricelist", + "pricelist_ids": [(6, 0, [self.pricelist1.id])], + "start_date": date_from, + "end_date": date_to, + "room_type_ids": [(6, 0, [room_type_double.id])], + "price": price, + "min_quantity": min_quantity, + "pms_property_ids": [self.pms_property1.id], + } + ).apply_massive_changes() + vals["date_start_consumption"] = date_from + vals["date_end_consumption"] = date_to + del vals["date_start"] + del vals["date_end"] + # ASSERT + for key in vals: + with self.subTest(k=key): + self.assertEqual( + self.pricelist1.item_ids[0][key], + vals[key], + "The value of " + key + " is not correctly established", + ) + + @freeze_time("1980-12-01") + def test_day_of_week_pricelist_items_create(self): + """ + Pricelist items for each day of week should be created. + """ + # ARRANGE + room_type_double = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": self.room_type_class1.id, + } + ) + test_case_week_days = [ + [1, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 0, 0, 1], + ] + date_from = fields.date.today() + date_to = date_from + datetime.timedelta(days=6) + wizard = self.env["pms.massive.changes.wizard"].create( + { + "massive_changes_on": "pricelist", + "pricelist_ids": [(6, 0, [self.pricelist1.id])], + "room_type_ids": [(6, 0, [room_type_double.id])], + "start_date": date_from, + "end_date": date_to, + "pms_property_ids": [self.pms_property1.id], + "price": 20, + } + ) + for index, test_case in enumerate(test_case_week_days): + with self.subTest(k=test_case): + # ARRANGE + wizard.write( + { + "apply_on_monday": test_case[0], + "apply_on_tuesday": test_case[1], + "apply_on_wednesday": test_case[2], + "apply_on_thursday": test_case[3], + "apply_on_friday": test_case[4], + "apply_on_saturday": test_case[5], + "apply_on_sunday": test_case[6], + } + ) + self.pricelist1.item_ids = False + # ACT + wizard.apply_massive_changes() + pricelist_items = self.pricelist1.item_ids.sorted( + key=lambda s: s.date_start_consumption + ) + # ASSERT + self.assertTrue( + pricelist_items[index].date_start_consumption.timetuple()[6] + == index + and test_case[index], + "Rule not created on correct day of week", + ) + + def test_several_pricelists(self): + """ + If several pricelist are set, the wizard should create as + many pricelist items as pricelists. + """ + # ARRANGE + room_type_double = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": self.room_type_class1.id, + } + ) + pricelist2 = self.env["product.pricelist"].create( + { + "name": "test pricelist 2", + "availability_plan_id": self.availability_plan1.id, + "is_pms_available": True, + } + ) + expected_pricelists = [self.pricelist1.id, pricelist2.id] + date_from = fields.date.today() + date_to = fields.date.today() + vals_wizard = { + "massive_changes_on": "pricelist", + "pricelist_ids": [(6, 0, [self.pricelist1.id, pricelist2.id])], + "room_type_ids": [(6, 0, [room_type_double.id])], + "pms_property_ids": [self.pms_property1.id], + "start_date": date_from, + "end_date": date_to, + "price": 20, + } + # ACT + self.env["pms.massive.changes.wizard"].create( + vals_wizard + ).apply_massive_changes() + # ASSERT + self.assertEqual( + set(expected_pricelists), + set( + self.env["product.pricelist.item"] + .search([("product_id", "=", room_type_double.product_id.id)]) + .mapped("pricelist_id") + .ids + ), + "The wizard should create as many items as pricelists given.", + ) + + def test_several_room_types_pricelist(self): + """ + If several room types are set, the wizard should create as + many pricelist items as room types. + """ + # ARRANGE + room_type_double = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": self.room_type_class1.id, + } + ) + room_type_single = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id], + "name": "Single Test", + "default_code": "SNG_Test", + "class_id": self.room_type_class1.id, + } + ) + date_from = fields.date.today() + date_to = fields.date.today() + expected_product_ids = [ + room_type_double.product_id.id, + room_type_single.product_id.id, + ] + vals_wizard = { + "massive_changes_on": "pricelist", + "pricelist_ids": [(6, 0, [self.pricelist1.id])], + "room_type_ids": [ + ( + 6, + 0, + [room_type_double.id, room_type_single.id], + ) + ], + "pms_property_ids": [self.pms_property1.id], + "start_date": date_from, + "end_date": date_to, + "price": 20, + } + # ACT + self.env["pms.massive.changes.wizard"].create( + vals_wizard + ).apply_massive_changes() + # ASSERT + self.assertEqual( + set(expected_product_ids), + set( + self.env["product.pricelist.item"] + .search([("pricelist_id", "=", self.pricelist1.id)]) + .mapped("product_id") + .ids + ), + "The wizard should create as many items as room types given.", + ) + + def test_one_board_service_room_type_no_board_service(self): + """ + Call to wizard with one board service room type and no + board service. + The wizard must create as many pricelist items as there + are services on the given board service. + """ + # ARRANGE + room_type_single = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": self.room_type_class1.id, + } + ) + board_service_only_breakfast = self.env["pms.board.service"].create( + { + "name": "Test Only Breakfast", + "default_code": "CB1", + "pms_property_ids": [self.pms_property1.id], + } + ) + service_breakfast = self.env["product.product"].create( + {"name": "Test Breakfast"} + ) + board_service_single = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": room_type_single.id, + "pms_board_service_id": board_service_only_breakfast.id, + "pms_property_id": self.pms_property1.id, + } + ) + board_service_line_single_1 = self.env["pms.board.service.line"].create( + { + "product_id": service_breakfast.id, + "pms_board_service_id": board_service_only_breakfast.id, + "adults": True, + } + ) + date_from = fields.date.today() + date_to = fields.date.today() + wizard_result = self.env["pms.massive.changes.wizard"].create( + { + "massive_changes_on": "pricelist", + "pricelist_ids": [(6, 0, [self.pricelist1.id])], + "apply_pricelists_on": "board_services", + "board_service_room_type_ids": [ + ( + 6, + 0, + [board_service_single.id], + ) + ], + "pms_property_ids": [self.pms_property1.id], + "start_date": date_from, + "end_date": date_to, + "date_types": "consumption_dates", + } + ) + # ACT + wizard_result.apply_massive_changes() + + items_created = self.env["product.pricelist.item"].search( + [ + ("pricelist_id", "=", self.pricelist1.id), + ("pms_property_ids", "=", self.pms_property1.id), + ("product_id", "=", board_service_line_single_1.product_id.id), + ] + ) + # ASSERT + self.assertIn( + service_breakfast, + items_created.mapped("product_id"), + "The wizard must create as many pricelist items as there " + "are services on the given board service.", + ) + + def test_one_board_service_room_type_with_board_service(self): + """ + Call to wizard with one board service room type and + board service. + The wizard must create one pricelist items with + the board service given. + """ + # ARRANGE + room_type_single = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": self.room_type_class1.id, + } + ) + board_service_only_breakfast = self.env["pms.board.service"].create( + { + "name": "Test Only Breakfast", + "default_code": "CB1", + "pms_property_ids": [self.pms_property1.id], + } + ) + service_breakfast = self.env["product.product"].create( + {"name": "Test Breakfast"} + ) + board_service_single = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": room_type_single.id, + "pms_board_service_id": board_service_only_breakfast.id, + "pms_property_id": self.pms_property1.id, + } + ) + board_service_line_single_1 = self.env["pms.board.service.line"].create( + { + "product_id": service_breakfast.id, + "pms_board_service_id": board_service_only_breakfast.id, + "adults": True, + } + ) + date_from = fields.date.today() + date_to = fields.date.today() + wizard_result = self.env["pms.massive.changes.wizard"].create( + { + "massive_changes_on": "pricelist", + "pricelist_ids": [(6, 0, [self.pricelist1.id])], + "apply_pricelists_on": "board_services", + "board_service_room_type_ids": [ + ( + 6, + 0, + [board_service_single.id], + ) + ], + "board_service": board_service_line_single_1.product_id.id, + "pms_property_ids": [self.pms_property1.id], + "start_date": date_from, + "end_date": date_to, + "date_types": "consumption_dates", + } + ) + # ACT + wizard_result.apply_massive_changes() + + items_created = self.env["product.pricelist.item"].search( + [ + ("pricelist_id", "=", self.pricelist1.id), + ("pms_property_ids", "=", self.pms_property1.id), + ("product_id", "=", board_service_line_single_1.product_id.id), + ] + ) + # ASSERT + self.assertIn( + service_breakfast, + items_created.mapped("product_id"), + "The wizard must create one pricelist items with " + " the board service given.", + ) + + def test_several_board_service_room_type_no_board_service(self): + """ + Call to wizard with several board service room type and no + board service. + The wizard must create as many pricelist items as there + are services on the given board services. + """ + # ARRANGE + room_type_single = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id], + "name": "Double Test", + "default_code": "SNG_Test", + "class_id": self.room_type_class1.id, + } + ) + room_type_double = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": self.room_type_class1.id, + } + ) + board_service_only_breakfast = self.env["pms.board.service"].create( + { + "name": "Test Only Breakfast", + "default_code": "CB1", + "pms_property_ids": [self.pms_property1.id], + } + ) + board_service_half_board = self.env["pms.board.service"].create( + { + "name": "Test Half Board", + "default_code": "CB2", + "pms_property_ids": [self.pms_property1.id], + } + ) + service_breakfast = self.env["product.product"].create( + {"name": "Test Breakfast"} + ) + service_dinner = self.env["product.product"].create({"name": "Test Dinner"}) + board_service_single = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": room_type_single.id, + "pms_board_service_id": board_service_only_breakfast.id, + "pms_property_id": self.pms_property1.id, + } + ) + board_service_double = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": room_type_double.id, + "pms_board_service_id": board_service_half_board.id, + "pms_property_id": self.pms_property1.id, + } + ) + self.env["pms.board.service.line"].create( + { + "product_id": service_breakfast.id, + "pms_board_service_id": board_service_only_breakfast.id, + "adults": True, + } + ) + self.env["pms.board.service.line"].create( + { + "product_id": service_breakfast.id, + "pms_board_service_id": board_service_half_board.id, + "adults": True, + } + ) + self.env["pms.board.service.line"].create( + { + "product_id": service_dinner.id, + "pms_board_service_id": board_service_half_board.id, + "adults": True, + } + ) + date_from = fields.date.today() + date_to = fields.date.today() + product_ids_expected = ( + board_service_double.pms_board_service_id.board_service_line_ids.mapped( + "product_id" + ).ids + + board_service_single.pms_board_service_id.board_service_line_ids.mapped( + "product_id" + ).ids + ) + wizard_result = self.env["pms.massive.changes.wizard"].create( + { + "massive_changes_on": "pricelist", + "pricelist_ids": [(6, 0, [self.pricelist1.id])], + "apply_pricelists_on": "board_services", + "board_service_room_type_ids": [ + ( + 6, + 0, + [ + board_service_single.id, + board_service_double.id, + ], + ) + ], + "pms_property_ids": [self.pms_property1.id], + "start_date": date_from, + "end_date": date_to, + "date_types": "consumption_dates", + } + ) + # ACT + wizard_result.apply_massive_changes() + items_created = self.env["product.pricelist.item"].search( + [ + ("pricelist_id", "=", self.pricelist1.id), + ("pms_property_ids", "=", self.pms_property1.id), + ("product_id", "in", product_ids_expected), + ] + ) + # ASSERT + self.assertEqual( + set(product_ids_expected), + set(items_created.mapped("product_id").ids), + "The wizard should create as many pricelist items as there" + " are services on the given board services.", + ) + + def test_several_board_service_room_type_with_board_service(self): + + """ + Call to wizard with several board service room types and + board service. + The wizard must create as many pricelist items as there + are services on the given board services. + """ + # ARRANGE + room_type_single = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id], + "name": "Double Test", + "default_code": "SNG_Test", + "class_id": self.room_type_class1.id, + } + ) + room_type_double = self.env["pms.room.type"].create( + { + "pms_property_ids": [self.pms_property1.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": self.room_type_class1.id, + } + ) + board_service_only_breakfast = self.env["pms.board.service"].create( + { + "name": "Test Only Breakfast", + "default_code": "CB1", + "pms_property_ids": [self.pms_property1.id], + } + ) + board_service_half_board = self.env["pms.board.service"].create( + { + "name": "Test Half Board", + "default_code": "CB2", + "pms_property_ids": [self.pms_property1.id], + } + ) + service_breakfast = self.env["product.product"].create( + {"name": "Test Breakfast"} + ) + service_dinner = self.env["product.product"].create({"name": "Test Dinner"}) + board_service_single = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": room_type_single.id, + "pms_board_service_id": board_service_only_breakfast.id, + "pms_property_id": self.pms_property1.id, + } + ) + board_service_double = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": room_type_double.id, + "pms_board_service_id": board_service_half_board.id, + "pms_property_id": self.pms_property1.id, + } + ) + self.env["pms.board.service.line"].create( + { + "product_id": service_breakfast.id, + "pms_board_service_id": board_service_only_breakfast.id, + "adults": True, + } + ) + self.env["pms.board.service.line"].create( + { + "product_id": service_breakfast.id, + "pms_board_service_id": board_service_half_board.id, + "adults": True, + } + ) + self.env["pms.board.service.line"].create( + { + "product_id": service_dinner.id, + "pms_board_service_id": board_service_half_board.id, + "adults": True, + } + ) + date_from = fields.date.today() + date_to = fields.date.today() + board_service_id_double = board_service_double.pms_board_service_id + board_service_id_single = board_service_single.pms_board_service_id + product_ids_expected = list( + set(board_service_id_double.board_service_line_ids.mapped("product_id").ids) + & set( + board_service_id_single.board_service_line_ids.mapped("product_id").ids + ) + ) + wizard_result = self.env["pms.massive.changes.wizard"].create( + { + "massive_changes_on": "pricelist", + "pricelist_ids": [(6, 0, [self.pricelist1.id])], + "apply_pricelists_on": "board_services", + "board_service_room_type_ids": [ + ( + 6, + 0, + [ + board_service_single.id, + board_service_double.id, + ], + ) + ], + "board_service": service_breakfast.id, + "pms_property_ids": [self.pms_property1.id], + "start_date": date_from, + "end_date": date_to, + "date_types": "consumption_dates", + } + ) + # ACT + wizard_result.apply_massive_changes() + + items_created = self.env["product.pricelist.item"].search( + [ + ("pricelist_id", "=", self.pricelist1.id), + ("pms_property_ids", "=", self.pms_property1.id), + ("product_id", "in", product_ids_expected), + ] + ) + # ASSERT + self.assertEqual( + set(product_ids_expected), + set(items_created.mapped("product_id").ids), + "The wizard should create as many pricelist items as there" + " are services on the given board services.", + ) + + def test_service(self): + """ + Call to wizard with one service (product_id) + The wizard must create one pricelist items with + the given service (product_id). + """ + # ARRANGE + service_spa = self.env["product.product"].create({"name": "Test Spa"}) + date_from = fields.date.today() + date_to = fields.date.today() + wizard_result = self.env["pms.massive.changes.wizard"].create( + { + "massive_changes_on": "pricelist", + "pricelist_ids": [(6, 0, [self.pricelist1.id])], + "apply_pricelists_on": "service", + "service": service_spa.id, + "pms_property_ids": [self.pms_property1.id], + "start_date": date_from, + "end_date": date_to, + "date_types": "consumption_dates", + } + ) + # ACT + wizard_result.apply_massive_changes() + items_created = self.env["product.pricelist.item"].search( + [ + ("pricelist_id", "=", self.pricelist1.id), + ("pms_property_ids", "=", self.pms_property1.id), + ("product_id", "=", service_spa.id), + ] + ) + # ASSERT + self.assertIn( + service_spa.id, + items_created.mapped("product_id").ids, + "The wizard should create one pricelist items with" + " the given service (product_id).", + ) + + def test_sale_dates(self): + """ + Call to wizard with one service (product_id) + and dates of SALE + The wizard must create one pricelist items with + the given service (product_id) and dates of SALE. + """ + # ARRANGE + service_spa = self.env["product.product"].create({"name": "Test Spa"}) + date_from = fields.date.today() + date_to = fields.date.today() + wizard_result = self.env["pms.massive.changes.wizard"].create( + { + "massive_changes_on": "pricelist", + "pricelist_ids": [(6, 0, [self.pricelist1.id])], + "apply_pricelists_on": "service", + "service": service_spa.id, + "pms_property_ids": [self.pms_property1.id], + "start_date": date_from, + "end_date": date_to, + "date_types": "sale_dates", + } + ) + # ACT + wizard_result.apply_massive_changes() + items_created = self.env["product.pricelist.item"].search( + [ + ("pricelist_id", "=", self.pricelist1.id), + ("pms_property_ids", "=", self.pms_property1.id), + ("product_id", "=", service_spa.id), + ] + ) + expected_dates = [ + datetime.datetime.combine(date_from, datetime.datetime.min.time()), + datetime.datetime.combine(date_to, datetime.datetime.max.time()), + ] + # ASSERT + self.assertEqual( + expected_dates, + items_created.mapped("date_start") + items_created.mapped("date_end"), + "The wizard should create one pricelist items with" + " the given service (product_id) and dates of sale.", + ) + + def test_several_properties_pricelist(self): + """ + If several properties are set, the wizard should create as + many items as properties. + """ + # ARRANGE + service_spa = self.env["product.product"].create({"name": "Test Spa"}) + pms_property2 = self.env["pms.property"].create( + { + "name": "MY 2nd PMS TEST", + "company_id": self.env.ref("base.main_company").id, + } + ) + date_from = fields.date.today() + date_to = fields.date.today() + expected_properties = [ + self.pms_property1.id, + pms_property2.id, + ] + vals_wizard = { + "massive_changes_on": "pricelist", + "pricelist_ids": [(6, 0, [self.pricelist1.id])], + "apply_pricelists_on": "service", + "service": service_spa.id, + "pms_property_ids": [(6, 0, [self.pms_property1.id, pms_property2.id])], + "start_date": date_from, + "end_date": date_to, + "date_types": "sale_dates", + } + # ACT + self.env["pms.massive.changes.wizard"].create( + vals_wizard + ).apply_massive_changes() + # ASSERT + self.assertEqual( + set(expected_properties), + set( + self.env["product.pricelist.item"] + .search([("pricelist_id", "=", self.pricelist1.id)]) + .mapped("pms_property_ids") + .ids + ), + "The wizard should create as many items as properties given.", + ) diff --git a/pms/tests/test_pms_wizard_split_join_swap_reservation.py b/pms/tests/test_pms_wizard_split_join_swap_reservation.py new file mode 100644 index 0000000000..eae35ebe2a --- /dev/null +++ b/pms/tests/test_pms_wizard_split_join_swap_reservation.py @@ -0,0 +1,1020 @@ +import datetime + +from odoo.exceptions import UserError + +from .common import TestPms + + +class TestPmsWizardSplitJoinSwapReservation(TestPms): + @classmethod + def setUpClass(cls): + super().setUpClass() + # pms.availability.plan + cls.test_availability_plan = cls.env["pms.availability.plan"].create( + { + "name": "Availability plan for TEST", + "pms_pricelist_ids": [(6, 0, [cls.pricelist1.id])], + } + ) + + # pms.room.type + cls.test_room_type_single = cls.env["pms.room.type"].create( + { + "pms_property_ids": [cls.pms_property1.id], + "name": "Single Test", + "default_code": "SNG_Test", + "class_id": cls.room_type_class1.id, + } + ) + # pms.room.type + cls.test_room_type_double = cls.env["pms.room.type"].create( + { + "pms_property_ids": [cls.pms_property1.id], + "name": "Double Test", + "default_code": "DBL_Test", + "class_id": cls.room_type_class1.id, + } + ) + + # create rooms + cls.room1 = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property1.id, + "name": "Double 101", + "room_type_id": cls.test_room_type_double.id, + "capacity": 2, + } + ) + + cls.room2 = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property1.id, + "name": "Double 102", + "room_type_id": cls.test_room_type_double.id, + "capacity": 2, + } + ) + + cls.partner1 = cls.env["res.partner"].create({"name": "Antón"}) + + # create a sale channel + cls.sale_channel_direct1 = cls.env["pms.sale.channel"].create( + { + "name": "Door", + "channel_type": "direct", + } + ) + + # UNIFY TESTS # review + def test_unify_reservation_avail_should(self): + """ + Check that, if there is availability, a reservation with several + rooms on different days can be unified into a one room reservation. + ------------ + Create a reservation with room1.Then, in the first reservation line, + the room is changed to room2.The reservation_join() method of the wizard + is launched, passing the reservation and room2 as parameters and it is + verified that room2 is found in all the reservation lines. + + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | r1 | | r1 | | | | + | Double 102 | | r1 | | | | | + +------------+------+------+------+----+----+----+ + """ + # ARRANGE + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room1.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r1.flush() + r1.reservation_line_ids[0].room_id = self.room2 + # ACT + self.env["pms.reservation.split.join.swap.wizard"].reservation_join( + r1, self.room2 + ) + # ASSERT + self.assertEqual( + r1.reservation_line_ids.mapped("room_id"), + self.room2, + "The unify operation should assign the indicated room to all nights", + ) + + def test_unify_reservation_avail_not(self): + """ + Check that you cannot unify a reservation with two different rooms + because there is no availability in the required room. + ---------- + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | r1 | r1 | r2 | | | | + | Double 102 | r0 | r0 | r1 | | | | + +------------+------+------+------+----+----+----+ + """ + # ARRANGE + self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=2), + "adults": 2, + "preferred_room_id": self.room2.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r2 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now() + datetime.timedelta(days=2), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room1.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "room_type_id": self.test_room_type_double.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r2.flush() + # ACT & ASSERT + with self.assertRaises(UserError): + self.env["pms.reservation.split.join.swap.wizard"].reservation_join( + r1, self.room1 + ) + + def test_unify_reservation_avail_not_room_exist(self): + """ + Check that you cannot unify a reservation with two different rooms + because there the required room does not exists. + """ + + # ARRANGE + + self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room1.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r2 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room2.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r2.flush() + with self.assertRaises(UserError): + self.env["pms.reservation.split.join.swap.wizard"].reservation_join( + r2, self.env["pms.room"] + ) + + # SWAP TESTS + def test_swap_reservation_rooms_01(self): + # TEST CASE + """ + Check that the rooms of two different reservations was swapped correctly + by applying the reservations_swap() method of the wizard. + ------------ + Initial state + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | r1 | r1 | r1 | | | | + | Double 102 | r2 | r2 | r2 | | | | + +------------+------+------+------+----+----+----+ + + State after swap + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | r2 | r2 | r2 | | | | + | Double 102 | r1 | r1 | r1 | | | | + +------------+------+------+------+----+----+----+ + """ + # ARRANGE + + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room1.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r2 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room2.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r1.flush() + r2.flush() + # ACT + self.env["pms.reservation.split.join.swap.wizard"].reservations_swap( + datetime.datetime.now(), + datetime.datetime.now() + datetime.timedelta(days=3), + self.room1.id, + self.room2.id, + ) + # ASSERT + self.assertTrue( + r1.reservation_line_ids.room_id == self.room2 + and r2.reservation_line_ids.room_id == self.room1 + ) + + def test_swap_reservation_rooms_02(self): + """ + Check that two rooms from two different reservations are swapped + correctly. + ------------------- + + Initial state + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | | r1 | r1 | | | | + | Double 102 | r2 | r2 | r2 | | | | + +------------+------+------+------+----+----+----+ + + State after swap + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | r2 | r2 | r2 | | | | + | Double 102 | | r1 | r1 | | | | + +------------+------+------+------+----+----+----+ + """ + # ARRANGE + + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now() + datetime.timedelta(days=1), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room1.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r2 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room2.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r1.flush() + r2.flush() + # ACT + self.env["pms.reservation.split.join.swap.wizard"].reservations_swap( + datetime.datetime.now(), + datetime.datetime.now() + datetime.timedelta(days=3), + self.room1.id, + self.room2.id, + ) + # ASSERT + self.assertTrue( + r1.reservation_line_ids.room_id == self.room2 + and r2.reservation_line_ids.room_id == self.room1 + ) + + def test_swap_reservation_rooms_03(self): + """ + Check that two rooms from two different reservations are swapped + correctly. + ------------------- + Initial state + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | | r1 | r1 | | | | + | Double 102 | r2 | r2 | r2 | | | | + +------------+------+------+------+----+----+----+ + + State after swap + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | r2 | r2 | r2 | | | | + | Double 102 | | r1 | r1 | | | | + +------------+------+------+------+----+----+----+ + """ + # ARRANGE + + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now() + datetime.timedelta(days=1), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room1.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r2 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room2.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r1.flush() + r2.flush() + # ACT + self.env["pms.reservation.split.join.swap.wizard"].reservations_swap( + datetime.datetime.now(), + datetime.datetime.now() + datetime.timedelta(days=3), + self.room2.id, + self.room1.id, + ) + # ASSERT + self.assertTrue( + r1.reservation_line_ids.room_id == self.room2 + and r2.reservation_line_ids.room_id == self.room1 + ) + + def test_swap_reservation_rooms_04(self): + # TEST CASE + """ + Check that two rooms from two different reservations are swapped + correctly. + source: r1 + target: r2 + -------- + + Initial state + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | r1 | r1 | | | | | + | Double 102 | r2 | r2 | r2 | | | | + +------------+------+------+------+----+----+----+ + + State after swap + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | r2 | r2 | | | | | + | Double 102 | r1 | r1 | r2 | | | | + +------------+------+------+------+----+----+----+ + """ + # ARRANGE + + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=2), + "adults": 2, + "preferred_room_id": self.room1.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r2 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room2.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r1.flush() + r2.flush() + # ACT + self.env["pms.reservation.split.join.swap.wizard"].reservations_swap( + datetime.datetime.now(), + datetime.datetime.now() + datetime.timedelta(days=3), + self.room1.id, + self.room2.id, + ) + # ASSERT + self.assertTrue( + r1.reservation_line_ids.room_id == self.room2 + and r2.reservation_line_ids.room_id == self.room1 + ) + + def test_swap_reservation_rooms_05(self): + """ + Check that two rooms from two different reservations are swapped + correctly. + source: r2 + target: r1 + --------------- + + Initial state + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | r1 | r1 | | | | | + | Double 102 | r2 | r2 | r2 | | | | + +------------+------+------+------+----+----+----+ + + State after swap + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | r2 | r2 | r2 | | | | + | Double 102 | r1 | r1 | | | | | + +------------+------+------+------+----+----+----+ + + """ + # ARRANGE + + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=2), + "adults": 2, + "preferred_room_id": self.room1.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r2 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room2.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r1.flush() + r2.flush() + # ACT + self.env["pms.reservation.split.join.swap.wizard"].reservations_swap( + datetime.datetime.now(), + datetime.datetime.now() + datetime.timedelta(days=3), + self.room2.id, + self.room1.id, + ) + # ASSERT + self.assertTrue( + r1.reservation_line_ids.room_id == self.room2 + and r2.reservation_line_ids.room_id == self.room1 + ) + + def test_swap_reservation_rooms_06(self): + """ + Check that the room is exchanged correctly for every day because there + is no reservation for another room in those days. + --------------------------- + + Initial state + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | | | | | | | + | Double 102 | r1 | r1 | r1 | | | | + +------------+------+------+------+----+----+----+ + + State after swap + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | r1 | r1 | r1 | | | | + | Double 102 | | | | | | | + +------------+------+------+------+----+----+----+ + """ + # ARRANGE + + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room2.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + # ACT + self.env["pms.reservation.split.join.swap.wizard"].reservations_swap( + datetime.datetime.now(), + datetime.datetime.now() + datetime.timedelta(days=3), + self.room2.id, + self.room1.id, + ) + # ASSERT + self.assertTrue(r1.reservation_line_ids.room_id == self.room1) + + def test_swap_reservation_rooms_gap_01(self): + """ + Check that three rooms from three different reservations are swapped + correctly. + ----------- + + Initial state + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | r0 | | r1 | | | | + | Double 102 | r2 | r2 | r2 | | | | + +------------+------+------+------+----+----+----+ + + State after swap + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | r2 | r2 | r2 | | | | + | Double 102 | r0 | | r1 | | | | + +------------+------+------+------+----+----+----+ + """ + + # ARRANGE + + r0 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "preferred_room_id": self.room1.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now() + datetime.timedelta(days=2), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room1.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r2 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room2.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r1.flush() + r2.flush() + # ACT + self.env["pms.reservation.split.join.swap.wizard"].reservations_swap( + datetime.datetime.now(), + datetime.datetime.now() + datetime.timedelta(days=3), + self.room1.id, + self.room2.id, + ) + # ASSERT + self.assertTrue( + r0.reservation_line_ids.room_id == self.room2 + and r1.reservation_line_ids.room_id == self.room2 + and r2.reservation_line_ids.room_id == self.room1 + ) + + def test_swap_reservation_rooms_gap_02(self): + # TEST CASE + """ + Check that three rooms from three different reservations are swapped + correctly. + ----------- + + Initial state + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | r0 | | r1 | | | | + | Double 102 | r2 | r2 | r2 | | | | + +------------+------+------+------+----+----+----+ + + State after swap + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | r2 | r2 | r2 | | | | + | Double 102 | r0 | | r1 | | | | + +------------+------+------+------+----+----+----+ + """ + # ARRANGE + + r0 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=1), + "adults": 2, + "preferred_room_id": self.room1.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now() + datetime.timedelta(days=2), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room1.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r2 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room2.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r1.flush() + r2.flush() + # ACT + self.env["pms.reservation.split.join.swap.wizard"].reservations_swap( + datetime.datetime.now(), + datetime.datetime.now() + datetime.timedelta(days=3), + self.room2.id, + self.room1.id, + ) + # ASSERT + self.assertTrue( + r0.reservation_line_ids.room_id == self.room2 + and r1.reservation_line_ids.room_id == self.room2 + and r2.reservation_line_ids.room_id == self.room1 + ) + + # NOT VALID TEST CASES + def test_swap_reservation_not_valid_01(self): + """ + Check that an error is thrown if you try to pass a room that is + not reserved for those days to the reservations_swap() method. + --------------------------- + Swap room1 with room2 should raise an error because room1 has + no reservation between checkin & checkout provided. + + Initial state + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | | | | | | | + | Double 102 | r1 | r1 | r1 | | | | + +------------+------+------+------+----+----+----+ + """ + # ARRANGE + + self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room2.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + # ASSERT & ACT + with self.assertRaises(UserError): + self.env["pms.reservation.split.join.swap.wizard"].reservations_swap( + datetime.datetime.now(), + datetime.datetime.now() + datetime.timedelta(days=3), + self.room1.id, + self.room2.id, + ) + + # SPLIT TESTS + def test_split_reservation_check_room_splitted_valid_01(self): + """ + A reservation is created with preferred room. The room for 1st night + is switched to another room. + ------------------- + + Expected result: + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | | r1 | r1 | | | | + | Double 102 | r1 | | | | | | + +------------+------+------+------+----+----+----+ + """ + # ARRANGE + + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room1.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r1.flush() + # ACT + self.env["pms.reservation.split.join.swap.wizard"].reservation_split( + r1, datetime.date.today(), self.room2 + ) + # ASSERT + self.assertTrue( + r1.reservation_line_ids[0].room_id == self.room2 + and r1.reservation_line_ids[1:].room_id == self.room1 + ) + + def test_split_reservation_check_room_splitted_valid_02(self): + """ + A reservation is created with preferred room. The room for 1st + night is switched to another room + -------------- + + Expected result: + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | r1 | r1 | | | | | + | Double 102 | | | r1 | | | | + +------------+------+------+------+----+----+----+ + """ + # ARRANGE + + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room1.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r1.flush() + # ACT + self.env["pms.reservation.split.join.swap.wizard"].reservation_split( + r1, + ( + datetime.datetime( + year=datetime.date.today().year, + month=datetime.date.today().month, + day=datetime.date.today().day, + ) + + datetime.timedelta(days=2) + ).date(), + self.room2, + ) + # ASSERT + self.assertTrue( + r1.reservation_line_ids[2].room_id == self.room2 + and r1.reservation_line_ids[:1].room_id == self.room1 + ) + + def test_split_reservation_check_room_splitted_valid_03(self): + """ + A reservation is created with preferred room. The room for 1st + night is switched to another room. + ----------- + + Expected result: + +------------+------+------+------+----+----+----+ + | room/date | 01 | 02 | 03 | 04 | 05 | 06 | + +------------+------+------+------+----+----+----+ + | Double 101 | r1 | | r1 | | | | + | Double 102 | | r1 | | | | | + +------------+------+------+------+----+----+----+""" + + # ARRANGE + + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room1.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r1.flush() + # ACT + self.env["pms.reservation.split.join.swap.wizard"].reservation_split( + r1, + ( + datetime.datetime( + year=datetime.date.today().year, + month=datetime.date.today().month, + day=datetime.date.today().day, + ) + + datetime.timedelta(days=1) + ).date(), + self.room2, + ) + # ASSERT + self.assertTrue( + r1.reservation_line_ids[1].room_id == self.room2 + and r1.reservation_line_ids[0].room_id == self.room1 + and r1.reservation_line_ids[2].room_id == self.room1 + ) + + def test_split_reservation_check_room_splitted_not_valid_01(self): + """ + Try to split the reservation for one night and set with a non valid room. + ---------- + Create a reservation for room1. Then create a room and it is deleted. The + reservation_split method is launched but an error should appear because + the room does not exist. + """ + + # ARRANGE + + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room1.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r1.flush() + room_not_exist = self.room3 = self.env["pms.room"].create( + { + "pms_property_id": self.pms_property1.id, + "name": "Double 103", + "room_type_id": self.test_room_type_double.id, + "capacity": 2, + } + ) + room_not_exist.unlink() + # ACT & ASSERT + with self.assertRaises(UserError): + self.env["pms.reservation.split.join.swap.wizard"].reservation_split( + r1, datetime.datetime.now(), room_not_exist + ) + + def test_split_reservation_check_room_splitted_not_valid_02(self): + # TEST CASE + """ + Try to split the reservation for one night and that night + doesn't belongto reservation. + --------------- + A reservation is created with a date interval of 3 days. + After the reservation_split() method is launched, passing + that reservation but with a date interval of 100 days, + this should throw an error. + """ + # ARRANGE + + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room1.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r1.flush() + # ACT & ASSERT + with self.assertRaises(UserError): + self.env["pms.reservation.split.join.swap.wizard"].reservation_split( + r1, datetime.datetime.now() + datetime.timedelta(days=100), self.room1 + ) + + def test_split_reservation_check_room_splitted_not_valid_03(self): + + """ + Try to split the reservation for one night and the reservation + not exists. + ------------- + A reservation is created, but it is not the reservation that is + passed to the reservation_split() method, one that does not exist + is passed to it, this should throw an error. + """ + + # ARRANGE + + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room1.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r1.flush() + # ACT & ASSERT + with self.assertRaises(UserError): + self.env["pms.reservation.split.join.swap.wizard"].reservation_split( + self.env["pms.reservation"], datetime.datetime.now(), self.room2 + ) + + def test_split_reservation_check_room_splitted_not_valid_04(self): + """ + Try to split the reservation to one room and the room is not available. + --------------- + A reservation is created with room2 as favorite_room. Another reservation + is created for the same days with room1. An attempt is made to separate + the room from the second reservation using the reservations_split() method, + passing it the same days as the reservations and room2, but this should + throw an error because room2 is not available for those days. + """ + # ARRANGE + + self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room2.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r1 = self.env["pms.reservation"].create( + { + "pms_property_id": self.pms_property1.id, + "checkin": datetime.datetime.now(), + "checkout": datetime.datetime.now() + datetime.timedelta(days=3), + "adults": 2, + "preferred_room_id": self.room1.id, + "partner_id": self.partner1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r1.flush() + # ACT & ASSERT + with self.assertRaises(UserError): + self.env["pms.reservation.split.join.swap.wizard"].reservation_split( + r1, datetime.datetime.now(), self.room2 + ) diff --git a/pms/tests/test_product_template.py b/pms/tests/test_product_template.py new file mode 100644 index 0000000000..c9a1d97831 --- /dev/null +++ b/pms/tests/test_product_template.py @@ -0,0 +1,327 @@ +import datetime + +from odoo import fields +from odoo.exceptions import ValidationError + +from .common import TestPms + + +class TestProductTemplate(TestPms): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.room_type = cls.env["pms.room.type"].create( + { + "name": "Room type test", + "default_code": "DBL_Test", + "class_id": cls.room_type_class1.id, + } + ) + cls.room = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property1.id, + "name": "Room test", + "room_type_id": cls.room_type.id, + "capacity": 2, + } + ) + cls.partner = cls.env["res.partner"].create({"name": "partner1"}) + cls.board_service = cls.env["pms.board.service"].create( + { + "name": "Board service test", + "default_code": "BST", + } + ) + # create a sale channel + cls.sale_channel_direct1 = cls.env["pms.sale.channel"].create( + { + "name": "Door", + "channel_type": "direct", + } + ) + + def test_bs_consumed_on_after(self): + """ + Create a one day reservation with a board service configured to + consume after reservation night. + Date of service line with consumed on 'after' should match checkout date. + """ + # ARRANGE + product = self.env["product.product"].create( + { + "name": "Product test", + "per_day": True, + "consumed_on": "after", + } + ) + self.env["pms.board.service.line"].create( + { + "product_id": product.id, + "pms_board_service_id": self.board_service.id, + "adults": True, + } + ) + board_service_room_type = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": self.room_type.id, + "pms_board_service_id": self.board_service.id, + } + ) + date_checkin = fields.date.today() + date_checkout = fields.date.today() + datetime.timedelta(days=1) + # ACT + reservation = self.env["pms.reservation"].create( + { + "checkin": date_checkin, + "checkout": date_checkout, + "room_type_id": self.room_type.id, + "pms_property_id": self.pms_property1.id, + "partner_id": self.partner.id, + "board_service_room_id": board_service_room_type.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + "adults": 2, + } + ) + # ASSERT + self.assertEqual( + reservation.service_ids.service_line_ids.date, + date_checkout, + "Date of service line with consumed on 'after' should match checkout date.", + ) + + def test_bs_consumed_on_before(self): + """ + Create a one day reservation with a board service configured to + consume before reservation night. + Date of service line with consumed on 'before' should match checkin date. + """ + # ARRANGE + product = self.env["product.product"].create( + { + "name": "Product test", + "per_day": True, + "consumed_on": "before", + } + ) + self.env["pms.board.service.line"].create( + { + "product_id": product.id, + "pms_board_service_id": self.board_service.id, + "adults": True, + } + ) + board_service_room_type = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": self.room_type.id, + "pms_board_service_id": self.board_service.id, + } + ) + date_checkin = fields.date.today() + date_checkout = fields.date.today() + datetime.timedelta(days=1) + # ACT + reservation = self.env["pms.reservation"].create( + { + "checkin": date_checkin, + "checkout": date_checkout, + "room_type_id": self.room_type.id, + "pms_property_id": self.pms_property1.id, + "partner_id": self.partner.id, + "board_service_room_id": board_service_room_type.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + "adults": 2, + } + ) + # ASSERT + self.assertEqual( + reservation.service_ids.service_line_ids.date, + date_checkin, + "Date of service line with consumed on 'before' should match checkin date.", + ) + + def test_bs_daily_limit_equal(self): + """ + Create a one day reservation with a board service configured with + daily limit = 2 and capacity = 2 + Reservation should created succesfully. + """ + # ARRANGE + product = self.env["product.product"].create( + { + "name": "Product test", + "per_day": True, + "daily_limit": 2, + "per_person": True, + } + ) + self.env["pms.board.service.line"].create( + { + "product_id": product.id, + "pms_board_service_id": self.board_service.id, + "adults": True, + } + ) + board_service_room_type = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": self.room_type.id, + "pms_board_service_id": self.board_service.id, + } + ) + date_checkin = fields.date.today() + date_checkout = fields.date.today() + datetime.timedelta(days=1) + # ACT + reservation = self.env["pms.reservation"].create( + { + "checkin": date_checkin, + "checkout": date_checkout, + "room_type_id": self.room_type.id, + "pms_property_id": self.pms_property1.id, + "partner_id": self.partner.id, + "board_service_room_id": board_service_room_type.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + reservation.flush() + # ASSERT + self.assertEqual( + reservation.service_ids.service_line_ids.day_qty, + self.room.capacity, + "The reservation should have been created.", + ) + + def test_bs_daily_limit_lower(self): + """ + Create a one day reservation with a board service configured with + daily limit = 2 and capacity = 1 + Reservation should created succesfully. + """ + # ARRANGE + self.room.capacity = 1 + product = self.env["product.product"].create( + { + "name": "Product test", + "per_day": True, + "daily_limit": 2, + "per_person": True, + } + ) + self.env["pms.board.service.line"].create( + { + "product_id": product.id, + "pms_board_service_id": self.board_service.id, + "adults": True, + } + ) + board_service_room_type = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": self.room_type.id, + "pms_board_service_id": self.board_service.id, + } + ) + date_checkin = fields.date.today() + date_checkout = fields.date.today() + datetime.timedelta(days=1) + # ACT + reservation = self.env["pms.reservation"].create( + { + "checkin": date_checkin, + "checkout": date_checkout, + "room_type_id": self.room_type.id, + "pms_property_id": self.pms_property1.id, + "partner_id": self.partner.id, + "board_service_room_id": board_service_room_type.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + reservation.flush() + # ASSERT + # self.assertTrue(reservation, "The reservation should have been created.") + # ASSERT + self.assertEqual( + reservation.service_ids.service_line_ids.day_qty, + self.room.capacity, + "The reservation should have been created.", + ) + + def test_bs_daily_limit_greater(self): + """ + Create a one day reservation with a board service configured with + daily limit = 1 and capacity = 2 + Reservation creation should fail. + """ + # ARRANGE + product = self.env["product.product"].create( + { + "name": "Product test", + "per_day": True, + "type": "service", + "daily_limit": 1, + "list_price": 15.0, + "per_person": True, + } + ) + self.env["pms.board.service.line"].create( + { + "product_id": product.id, + "pms_board_service_id": self.board_service.id, + "adults": True, + } + ) + board_service_room_type = self.env["pms.board.service.room.type"].create( + { + "pms_room_type_id": self.room_type.id, + "pms_board_service_id": self.board_service.id, + } + ) + date_checkin = fields.date.today() + date_checkout = fields.date.today() + datetime.timedelta(days=1) + # ACT & ASSERT + with self.assertRaises( + ValidationError, msg="Reservation created but it shouldn't" + ): + self.env["pms.reservation"].create( + { + "checkin": date_checkin, + "checkout": date_checkout, + "room_type_id": self.room_type.id, + "pms_property_id": self.pms_property1.id, + "partner_id": self.partner.id, + "board_service_room_id": board_service_room_type.id, + "adults": 2, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + # TODO: Review this test + def _test_bs_is_extra_bed(self): + # ARRANGE + product = self.env["product.product"].create( + { + "name": "Product test", + "per_day": True, + "consumed_on": "after", + "is_extra_bed": True, + } + ) + self.room.capacity = 1 + extra_bed_service = self.env["pms.service"].create( + { + "is_board_service": False, + "product_id": product.id, + } + ) + self.room.extra_beds_allowed = 1 + # ACT + reservation = self.env["pms.reservation"].create( + { + "checkin": fields.date.today(), + "checkout": fields.date.today() + datetime.timedelta(days=1), + "room_type_id": self.room_type.id, + "pms_property_id": self.pms_property1.id, + "partner_id": self.partner.id, + "service_ids": [extra_bed_service.id], + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + reservation._check_adults() + reservation.flush() + + # TODO: pending tests (need review) -> per_day, per_person (with board service?) diff --git a/pms/tests/test_shared_room.py b/pms/tests/test_shared_room.py new file mode 100644 index 0000000000..243990ce8b --- /dev/null +++ b/pms/tests/test_shared_room.py @@ -0,0 +1,509 @@ +import datetime + +from odoo import fields +from odoo.exceptions import ValidationError + +from .common import TestPms + + +class TestPmsSharedRoom(TestPms): + @classmethod + def setUpClass(cls): + super().setUpClass() + # create a room type availability + cls.room_type_availability = cls.env["pms.availability.plan"].create( + { + "name": "Availability plan for TEST", + "pms_pricelist_ids": [(6, 0, [cls.pricelist1.id])], + } + ) + + cls.bed_class = cls.env["pms.room.type.class"].create( + { + "name": "Bed Class 1", + "default_code": "B1", + } + ) + + # create room type + cls.room_type_test = cls.env["pms.room.type"].create( + { + "pms_property_ids": [cls.pms_property1.id], + "name": "Shared Test", + "default_code": "SHT", + "class_id": cls.room_type_class1.id, + } + ) + + cls.room_type_bed = cls.env["pms.room.type"].create( + { + "pms_property_ids": [cls.pms_property1.id], + "name": "Bed Type Test", + "default_code": "BTT", + "class_id": cls.bed_class.id, + } + ) + + # create shared room + cls.room1 = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property1.id, + "name": "Shared 101", + "room_type_id": cls.room_type_test.id, + "capacity": 2, + } + ) + + # create beds in room1 + cls.r1bed1 = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property1.id, + "name": "101 (1)", + "room_type_id": cls.room_type_bed.id, + "capacity": 1, + "parent_id": cls.room1.id, + } + ) + + cls.r1bed2 = cls.env["pms.room"].create( + { + "pms_property_id": cls.pms_property1.id, + "name": "101 (2)", + "room_type_id": cls.room_type_bed.id, + "capacity": 2, + "parent_id": cls.room1.id, + } + ) + + # create partner + cls.partner1 = cls.env["res.partner"].create( + { + "firstname": "Jaime", + "lastname": "García", + "email": "jaime@example.com", + "birthdate_date": "1983-03-01", + "gender": "male", + } + ) + + # create a sale channel + cls.sale_channel_direct1 = cls.env["pms.sale.channel"].create( + { + "name": "Door", + "channel_type": "direct", + } + ) + + def test_count_avail_beds_with_room_occupied(self): + """ + Check that not allow to create a bed reservation with a room occupied + ---------------- + Create a room1 reservation and check that the beds room real avail is 0 + """ + + # ARRANGE + today = fields.date.today() + tomorrow = fields.date.today() + datetime.timedelta(days=1) + + # ACT + self.env["pms.reservation"].create( + { + "partner_id": self.partner1.id, + "preferred_room_id": self.room1.id, + "checkin": today, + "checkout": tomorrow, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + # ASSERT + self.assertEqual( + self.pms_property1.with_context( + checkin=today, + checkout=tomorrow, + room_type_id=self.room_type_bed.id, + ).availability, + 0, + "Beds avaialbility should be 0 for room occupied", + ) + + def test_count_avail_shared_room_with_one_bed_occupied(self): + """ + Check that not allow to create a shared room reservation with a bed occupied + ---------------- + Create a room1's bed reservation and check that the room1 real avail is 0 + """ + + # ARRANGE + today = fields.date.today() + tomorrow = fields.date.today() + datetime.timedelta(days=1) + + # ACT + self.env["pms.reservation"].create( + { + "partner_id": self.partner1.id, + "preferred_room_id": self.r1bed1.id, + "checkin": today, + "checkout": tomorrow, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + # ASSERT + self.assertEqual( + self.pms_property1.with_context( + checkin=today, + checkout=tomorrow, + room_type_id=self.room_type_test.id, + ).availability, + 0, + "Shared Room avaialbility should be 0 if it has a bed occupied", + ) + + def test_avail_in_room_type_with_shared_rooms(self): + """ + Check that a shared room's bed occupied not + affect the avail on other rooms with the + same room type + ---------------- + Create other room like room_type_test (room2) + Create a room1's bed reservation and check that the room1 + Check that room_type_test real avail is 1 + """ + + # ARRANGE + today = fields.date.today() + tomorrow = fields.date.today() + datetime.timedelta(days=1) + self.room2 = self.env["pms.room"].create( + { + "pms_property_id": self.pms_property1.id, + "name": "Shared 102", + "room_type_id": self.room_type_test.id, + "capacity": 2, + } + ) + + # ACT + self.env["pms.reservation"].create( + { + "partner_id": self.partner1.id, + "preferred_room_id": self.r1bed1.id, + "checkin": today, + "checkout": tomorrow, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + # ASSERT + self.assertEqual( + self.pms_property1.with_context( + checkin=today, + checkout=tomorrow, + room_type_id=self.room_type_test.id, + ).availability, + 1, + "Room not shared affect by the shared room's avail with the same type", + ) + + def test_count_avail_beds_with_one_bed_occupied(self): + """ + Check the avail of a bed when it has + a room with other beds occupied + ---------------- + Create a room1's bed (it has 2 beds) + reservation and check that the beds avail = 1 + """ + + # ARRANGE + today = fields.date.today() + tomorrow = fields.date.today() + datetime.timedelta(days=1) + + # ACT + res1 = self.env["pms.reservation"].create( + { + "partner_id": self.partner1.id, + "preferred_room_id": self.r1bed1.id, + "checkin": today, + "checkout": tomorrow, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + res1.flush() + # ASSERT + self.assertEqual( + self.pms_property1.with_context( + checkin=today, + checkout=tomorrow, + room_type_id=self.room_type_bed.id, + ).availability, + 1, + "Beds avaialbility should be 1 if it has 1 of 2 beds occupied", + ) + + def test_not_avail_beds_with_room_occupied(self): + """ + Check that not allow to select a bed with a room occupied + ---------------- + Create a room1 reservation and check that the beds are not available + """ + + # ARRANGE + today = fields.date.today() + tomorrow = fields.date.today() + datetime.timedelta(days=1) + + # ACT + self.env["pms.reservation"].create( + { + "partner_id": self.partner1.id, + "preferred_room_id": self.room1.id, + "checkin": today, + "checkout": tomorrow, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + # ASSERT + self.assertNotIn( + self.r1bed1.id, + self.pms_property1.with_context( + checkin=today, + checkout=tomorrow, + room_type_id=self.room_type_bed.id, + ).free_room_ids.ids, + "room's bed should not be available " "because the entire room is reserved", + ) + + def test_not_avail_shared_room_with_one_bed_occupied(self): + """ + Check that not allow to select a shared + room with a bed occupied + ---------------- + Create a room1's bed reservation and check + that the room1 real avail is not available + """ + + # ARRANGE + today = fields.date.today() + tomorrow = fields.date.today() + datetime.timedelta(days=1) + + # ACT + self.env["pms.reservation"].create( + { + "partner_id": self.partner1.id, + "preferred_room_id": self.r1bed1.id, + "checkin": today, + "checkout": tomorrow, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + # ASSERT + self.assertNotIn( + self.room1.id, + self.pms_property1.with_context( + checkin=today, + checkout=tomorrow, + room_type_id=self.room_type_bed.id, + ).free_room_ids.ids, + "Entire Shared room should not be available " + "becouse it has a bed occupied", + ) + + def test_avail_beds_with_one_bed_occupied(self): + """ + Check the select of a bed when it has a + room with other beds occupied + ---------------- + Create a room1's bed (it has 2 beds) reservation + and check that the other bed is avail + """ + + # ARRANGE + today = fields.date.today() + tomorrow = fields.date.today() + datetime.timedelta(days=1) + + # ACT + self.env["pms.reservation"].create( + { + "partner_id": self.partner1.id, + "preferred_room_id": self.r1bed1.id, + "checkin": today, + "checkout": tomorrow, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + # ASSERT + self.assertIn( + self.r1bed2.id, + self.pms_property1.with_context( + checkin=today, + checkout=tomorrow, + room_type_id=self.room_type_bed.id, + ).free_room_ids.ids, + "The bed2 of the shared room should be available", + ) + + def test_not_allowed_reservation_in_bed_with_room_occuppied(self): + """ + Check the constrain that not allow to create a reservation in a bed in a + room with other reservation like shared + ---------------- + Create a room1's reservation and the try to create a reservation + in the room1's bed, we expect an error + """ + + # ARRANGE + today = fields.date.today() + tomorrow = fields.date.today() + datetime.timedelta(days=1) + + self.env["pms.reservation"].create( + { + "partner_id": self.partner1.id, + "preferred_room_id": self.room1.id, + "checkin": today, + "checkout": tomorrow, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + # ACT & ASSERT + with self.assertRaises( + ValidationError, + msg="Reservation created on a bed whose room was already occupied", + ): + r_test = self.env["pms.reservation"].create( + { + "partner_id": self.partner1.id, + "preferred_room_id": self.r1bed1.id, + "checkin": today, + "checkout": tomorrow, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r_test.flush() + + def test_not_allowed_reservation_in_shared_room_with_bed_occuppied(self): + """ + Check the constrain that not allow to create a reservation + in a shared room in a bed reservation + ---------------- + Create a room1's bed reservation and the try to create + a reservation in the room1, we expect an error + """ + + # ARRANGE + today = fields.date.today() + tomorrow = fields.date.today() + datetime.timedelta(days=1) + + self.env["pms.reservation"].create( + { + "partner_id": self.partner1.id, + "preferred_room_id": self.r1bed1.id, + "checkin": today, + "checkout": tomorrow, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + + # ACT & ASSERT + with self.assertRaises( + ValidationError, + msg="Reservation created in a full shared " + "room that already had beds occupied", + ): + r_test = self.env["pms.reservation"].create( + { + "partner_id": self.partner1.id, + "preferred_room_id": self.room1.id, + "checkin": today, + "checkout": tomorrow, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r_test.flush() + + def check_room_shared_availability_released_when_canceling_bed_reservations(self): + """ + Check that check availability in shared room is + released when canceling bed reservations + ---------------- + Create a room1's bed reservation and then cancel it, + check that the room1 real avail is 1 + """ + + # ARRANGE + today = fields.date.today() + tomorrow = fields.date.today() + datetime.timedelta(days=1) + + # ACT + r1 = self.env["pms.reservation"].create( + { + "partner_id": self.partner1.id, + "preferred_room_id": self.r1bed1.id, + "checkin": today, + "checkout": tomorrow, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r1.action_cancel() + + # ASSERT + self.assertEqual( + self.pms_property1.with_context( + checkin=today, + checkout=tomorrow, + room_type_id=self.room_type_test.id, + ).availability, + 1, + "The parent room avail dont update " "when cancel child room reservation", + ) + + def check_bed_availability_released_when_canceling_parent_room_reservations(self): + """ + Check that check availability in child room is + released when canceling the parent rooms + ---------------- + Create a room1 reservation and then cancel it, + check that the beds real avail is 2 + """ + + # ARRANGE + today = fields.date.today() + tomorrow = fields.date.today() + datetime.timedelta(days=1) + + # ACT + r1 = self.env["pms.reservation"].create( + { + "partner_id": self.partner1.id, + "preferred_room_id": self.room1.id, + "checkin": today, + "checkout": tomorrow, + "pms_property_id": self.pms_property1.id, + "sale_channel_origin_id": self.sale_channel_direct1.id, + } + ) + r1.action_cancel() + + # ASSERT + self.assertEqual( + self.pms_property1.with_context( + checkin=today, + checkout=tomorrow, + room_type_id=self.room_type_bed.id, + ).availability, + 2, + "The child room avail dont update when " "cancel parent room reservation", + ) diff --git a/pms/views/account_analytic_distribution_views.xml b/pms/views/account_analytic_distribution_views.xml new file mode 100644 index 0000000000..2838673780 --- /dev/null +++ b/pms/views/account_analytic_distribution_views.xml @@ -0,0 +1,21 @@ + + + + + account.analytic.tag.form.add_distribution_tags + account.analytic.tag + + + + + + + + + diff --git a/pms/views/account_analytic_line_views.xml b/pms/views/account_analytic_line_views.xml new file mode 100644 index 0000000000..9dfb879dbf --- /dev/null +++ b/pms/views/account_analytic_line_views.xml @@ -0,0 +1,18 @@ + + + + + account.analytic.line.tree.inherit.account + account.analytic.line + + + + + + + + + diff --git a/pms/views/account_bank_statement_views.xml b/pms/views/account_bank_statement_views.xml new file mode 100644 index 0000000000..31e0983843 --- /dev/null +++ b/pms/views/account_bank_statement_views.xml @@ -0,0 +1,13 @@ + + + + account.bank.statement + + + + + + + + + diff --git a/pms/views/account_journal_views.xml b/pms/views/account_journal_views.xml new file mode 100644 index 0000000000..5bc5fa053f --- /dev/null +++ b/pms/views/account_journal_views.xml @@ -0,0 +1,48 @@ + + + + account.journal + + + + + + + + + + + + account.journal + + + + + + + + + + account.journal + + + + + + + + diff --git a/pms/views/account_move_line_views.xml b/pms/views/account_move_line_views.xml new file mode 100644 index 0000000000..a272e096a1 --- /dev/null +++ b/pms/views/account_move_line_views.xml @@ -0,0 +1,31 @@ + + + + + account.move.line + + + + + + + + + + account.move.line + + + + + + + + + + + + diff --git a/pms/views/account_move_views.xml b/pms/views/account_move_views.xml new file mode 100644 index 0000000000..acfa6e4445 --- /dev/null +++ b/pms/views/account_move_views.xml @@ -0,0 +1,125 @@ + + + + account.move + + + + + + + + + + + + + + + + + + + + + + account.move + + + + + + + + + + + account.move + + + + + + + + + + + + + + account.move + + + + + + + + + + + + + + PMS Invoices + account.move + tree,kanban,form + + + [('move_type', 'in', ('out_invoice','out_refund'))] + {'default_move_type': 'out_invoice'} + +

+ Create a customer invoice +

+ Create invoices, register payments and keep track of the discussions with your customers. +

+
+
+ + +
diff --git a/pms/views/account_payment_views.xml b/pms/views/account_payment_views.xml new file mode 100644 index 0000000000..790b01130d --- /dev/null +++ b/pms/views/account_payment_views.xml @@ -0,0 +1,62 @@ + + + + account.payment + + + + + + + + + + + + + + + diff --git a/pms/views/account_portal_templates.xml b/pms/views/account_portal_templates.xml new file mode 100644 index 0000000000..3315c652d5 --- /dev/null +++ b/pms/views/account_portal_templates.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/pms/views/assets.xml b/pms/views/assets.xml new file mode 100644 index 0000000000..adf5c24dcc --- /dev/null +++ b/pms/views/assets.xml @@ -0,0 +1,15 @@ + + + + + + diff --git a/pms/views/folio_portal_templates.xml b/pms/views/folio_portal_templates.xml new file mode 100644 index 0000000000..91bfd0a8f8 --- /dev/null +++ b/pms/views/folio_portal_templates.xml @@ -0,0 +1,396 @@ + + + + + + + + + + +