From 7e1c48b0657c4dcc3eb8f619cc5bd0478635286f Mon Sep 17 00:00:00 2001 From: Maxime Chambreuil Date: Fri, 19 Nov 2021 16:25:59 -0600 Subject: [PATCH 01/28] [ADD] pms_base --- pms_base/README.rst | 109 ++++++ pms_base/__init__.py | 2 + pms_base/__manifest__.py | 38 +++ pms_base/data/pms_amenity_type.xml | 15 + pms_base/data/pms_room_type.xml | 78 +++++ pms_base/data/pms_stage.xml | 23 ++ pms_base/data/pms_team.xml | 7 + pms_base/models/__init__.py | 19 ++ pms_base/models/pms_amenity.py | 33 ++ pms_base/models/pms_amenity_type.py | 14 + pms_base/models/pms_property.py | 109 ++++++ pms_base/models/pms_room.py | 47 +++ pms_base/models/pms_room_type.py | 17 + pms_base/models/pms_service.py | 38 +++ pms_base/models/pms_stage.py | 50 +++ pms_base/models/pms_tag.py | 33 ++ pms_base/models/pms_team.py | 41 +++ pms_base/models/res_company.py | 15 + pms_base/models/res_config_settings.py | 39 +++ pms_base/models/res_partner.py | 9 + pms_base/readme/CONFIGURE.rst | 1 + pms_base/readme/CONTRIBUTORS.rst | 14 + pms_base/readme/DESCRIPTION.rst | 3 + pms_base/readme/USAGE.rst | 1 + pms_base/security/ir.model.access.csv | 19 ++ pms_base/security/ir_rule.xml | 13 + pms_base/security/res_groups.xml | 38 +++ pms_base/static/description/icon.png | Bin 0 -> 24626 bytes pms_base/static/description/index.html | 455 +++++++++++++++++++++++++ pms_base/views/menu.xml | 152 +++++++++ pms_base/views/pms_amenity.xml | 66 ++++ pms_base/views/pms_amenity_type.xml | 44 +++ pms_base/views/pms_property.xml | 283 +++++++++++++++ pms_base/views/pms_room.xml | 93 +++++ pms_base/views/pms_room_type.xml | 44 +++ pms_base/views/pms_service.xml | 82 +++++ pms_base/views/pms_stage.xml | 79 +++++ pms_base/views/pms_tag.xml | 55 +++ pms_base/views/pms_team.xml | 106 ++++++ pms_base/views/res_config_settings.xml | 230 +++++++++++++ pms_base/views/res_partner_view.xml | 47 +++ 41 files changed, 2561 insertions(+) create mode 100644 pms_base/README.rst create mode 100644 pms_base/__init__.py create mode 100644 pms_base/__manifest__.py create mode 100644 pms_base/data/pms_amenity_type.xml create mode 100644 pms_base/data/pms_room_type.xml create mode 100644 pms_base/data/pms_stage.xml create mode 100644 pms_base/data/pms_team.xml create mode 100644 pms_base/models/__init__.py create mode 100644 pms_base/models/pms_amenity.py create mode 100644 pms_base/models/pms_amenity_type.py create mode 100644 pms_base/models/pms_property.py create mode 100644 pms_base/models/pms_room.py create mode 100644 pms_base/models/pms_room_type.py create mode 100644 pms_base/models/pms_service.py create mode 100644 pms_base/models/pms_stage.py create mode 100644 pms_base/models/pms_tag.py create mode 100644 pms_base/models/pms_team.py create mode 100644 pms_base/models/res_company.py create mode 100644 pms_base/models/res_config_settings.py create mode 100644 pms_base/models/res_partner.py create mode 100644 pms_base/readme/CONFIGURE.rst create mode 100644 pms_base/readme/CONTRIBUTORS.rst create mode 100644 pms_base/readme/DESCRIPTION.rst create mode 100644 pms_base/readme/USAGE.rst create mode 100644 pms_base/security/ir.model.access.csv create mode 100644 pms_base/security/ir_rule.xml create mode 100644 pms_base/security/res_groups.xml create mode 100644 pms_base/static/description/icon.png create mode 100644 pms_base/static/description/index.html create mode 100644 pms_base/views/menu.xml create mode 100644 pms_base/views/pms_amenity.xml create mode 100644 pms_base/views/pms_amenity_type.xml create mode 100644 pms_base/views/pms_property.xml create mode 100644 pms_base/views/pms_room.xml create mode 100644 pms_base/views/pms_room_type.xml create mode 100644 pms_base/views/pms_service.xml create mode 100644 pms_base/views/pms_stage.xml create mode 100644 pms_base/views/pms_tag.xml create mode 100644 pms_base/views/pms_team.xml create mode 100644 pms_base/views/res_config_settings.xml create mode 100644 pms_base/views/res_partner_view.xml diff --git a/pms_base/README.rst b/pms_base/README.rst new file mode 100644 index 0000000000..c7dc5bd70c --- /dev/null +++ b/pms_base/README.rst @@ -0,0 +1,109 @@ +================================ +PMS (Property Management System) +================================ + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |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/pms + :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-pms + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/293/14.0 + :alt: Try me on Runbot + +|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. + +.. IMPORTANT:: + This is an alpha version, the data model and design can change at any time without warning. + Only for development or testing purpose, do not use in production. + `More details on development status `_ + +**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 smashing 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 + +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_base/__init__.py b/pms_base/__init__.py new file mode 100644 index 0000000000..02179fb044 --- /dev/null +++ b/pms_base/__init__.py @@ -0,0 +1,2 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from . import models diff --git a/pms_base/__manifest__.py b/pms_base/__manifest__.py new file mode 100644 index 0000000000..9dcc9a6885 --- /dev/null +++ b/pms_base/__manifest__.py @@ -0,0 +1,38 @@ +# Copyright 2019 Darío Lodeiros, Alexandre Díaz, Jose Luis Algara, Pablo Quesada +# Copyright (c) 2021 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Property Management System", + "summary": "Manage properties", + "version": "14.0.1.0.0", + "development_status": "Alpha", + "category": "Generic Modules/Property Management System", + "website": "https://github.com/OCA/pms", + "author": "Commit [Sun], Open Source Integrators, Odoo Community Association (OCA)", + "maintainers": ["eantones"], + "license": "AGPL-3", + "application": True, + "depends": ["base_geolocalize", "mail", "product"], + "data": [ + "security/res_groups.xml", + "security/ir.model.access.csv", + "security/ir_rule.xml", + "data/pms_stage.xml", + "data/pms_team.xml", + "data/pms_amenity_type.xml", + "data/pms_room_type.xml", + "views/pms_tag.xml", + "views/pms_stage.xml", + "views/pms_amenity_type.xml", + "views/pms_amenity.xml", + "views/pms_room_type.xml", + "views/pms_room.xml", + "views/pms_service.xml", + "views/pms_property.xml", + "views/res_config_settings.xml", + "views/pms_team.xml", + "views/menu.xml", + "views/res_partner_view.xml", + ], +} diff --git a/pms_base/data/pms_amenity_type.xml b/pms_base/data/pms_amenity_type.xml new file mode 100644 index 0000000000..65757707b8 --- /dev/null +++ b/pms_base/data/pms_amenity_type.xml @@ -0,0 +1,15 @@ + + + + Toiletries + + + + Connectivity + + + + Kitchen facilities + + + diff --git a/pms_base/data/pms_room_type.xml b/pms_base/data/pms_room_type.xml new file mode 100644 index 0000000000..18615b0fa7 --- /dev/null +++ b/pms_base/data/pms_room_type.xml @@ -0,0 +1,78 @@ + + + + Economic + + + + Single + + + + Double + + + + Triple + + + + Conference Room + + + + Bathroom + 10 + + + + Bedroom + 20 + + + + Kitchen + 30 + + + + Livingroom + 40 + + + + Diningroom + 50 + + + + Playroom + 60 + + + + Terrace/Patio + 64 + + + + Balcony + 67 + + + + Hall + 70 + + + + Garage + 80 + + + + Basement + 90 + + + diff --git a/pms_base/data/pms_stage.xml b/pms_base/data/pms_stage.xml new file mode 100644 index 0000000000..4aaae69de9 --- /dev/null +++ b/pms_base/data/pms_stage.xml @@ -0,0 +1,23 @@ + + + + New + 10 + True + property + + + + Available + 20 + property + + + + Cancelled + 99 + True + property + + + diff --git a/pms_base/data/pms_team.xml b/pms_base/data/pms_team.xml new file mode 100644 index 0000000000..40bb1af167 --- /dev/null +++ b/pms_base/data/pms_team.xml @@ -0,0 +1,7 @@ + + + + Default Team + + + diff --git a/pms_base/models/__init__.py b/pms_base/models/__init__.py new file mode 100644 index 0000000000..a971276fa9 --- /dev/null +++ b/pms_base/models/__init__.py @@ -0,0 +1,19 @@ +# Copyright 2018 Alexandre Díaz +# Copyright 2018 Dario Lodeiros +# Copyright (c) 2021 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import ( + pms_stage, + pms_tag, + pms_team, + pms_room_type, + pms_amenity_type, + pms_room, + pms_amenity, + pms_service, + pms_property, + res_company, + res_config_settings, + res_partner, +) diff --git a/pms_base/models/pms_amenity.py b/pms_base/models/pms_amenity.py new file mode 100644 index 0000000000..ad8c8b5e57 --- /dev/null +++ b/pms_base/models/pms_amenity.py @@ -0,0 +1,33 @@ +# 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 PmsAmenity(models.Model): + _name = "pms.amenity" + _description = "Property Amenity" + + active = fields.Boolean( + string="Active", help="Determines if amenity is active", default=True + ) + name = fields.Char( + string="Name", help="Name of the amenity", required=True, translate=True + ) + property_ids = fields.Many2many( + string="Properties", + help="Properties with access to the amenity", + comodel_name="pms.property", + ondelete="restrict", + relation="pms_property_amenity_rel", + column1="amenity_id", + column2="property_id", + ) + type_id = fields.Many2one( + string="Type", + help="Organize amenities by type (multimedia, comfort, etc ...)", + comodel_name="pms.amenity.type", + ) + default_code = fields.Char( + string="Internal Reference", help="Internal unique identifier of the amenity" + ) diff --git a/pms_base/models/pms_amenity_type.py b/pms_base/models/pms_amenity_type.py new file mode 100644 index 0000000000..211a0cfb46 --- /dev/null +++ b/pms_base/models/pms_amenity_type.py @@ -0,0 +1,14 @@ +# Copyright 2017 Alexandre Díaz +# Copyright 2017 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class PmsRoomAmenityType(models.Model): + _name = "pms.amenity.type" + _description = "Amenity Type" + + active = fields.Boolean( + string="Active", help="Determines if amenity type is active", default=True + ) + name = fields.Char(string="Name", required=True, translate=True) diff --git a/pms_base/models/pms_property.py b/pms_base/models/pms_property.py new file mode 100644 index 0000000000..058152c89f --- /dev/null +++ b/pms_base/models/pms_property.py @@ -0,0 +1,109 @@ +# Copyright 2019 Pablo Quesada +# Copyright 2019 Dario Lodeiros +# Copyright (c) 2021 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import api, fields, models + +from odoo.addons.base.models.res_partner import _tz_get + + +class PmsProperty(models.Model): + _name = "pms.property" + _description = "Property" + _inherit = ["mail.thread", "mail.activity.mixin"] + _inherits = {"res.partner": "partner_id"} + + partner_id = fields.Many2one( + string="Property", + help="Current property", + comodel_name="res.partner", + required=True, + ondelete="cascade", + ) + owner_id = fields.Many2one( + string="Owner", + help="The owner of the property.", + comodel_name="res.partner", + required=True, + ) + parent_id = fields.Many2one(string="Parent Property", comodel_name="pms.property") + property_child_ids = fields.One2many( + "pms.property", "parent_id", string="Children Property" + ) + company_id = fields.Many2one(string="Company", comodel_name="res.company") + team_id = fields.Many2one(string="Team", comodel_name="pms.team") + room_ids = fields.One2many( + string="Rooms", + help="List of rooms in the property.", + comodel_name="pms.room", + inverse_name="property_id", + ) + room_count = fields.Integer(string="Number of rooms", compute="_compute_room_count") + amenity_ids = fields.Many2many( + string="Amenities", + help="Amenities available in this property", + comodel_name="pms.amenity", + ondelete="restrict", + relation="pms_property_amenity_rel", + column1="property_id", + column2="amenity_id", + ) + service_ids = fields.One2many( + string="Services", + help="List of services available in the property.", + comodel_name="pms.service", + inverse_name="property_id", + ) + tag_ids = fields.Many2many( + string="Tags", + comodel_name="pms.tag", + relation="pms_property_tag_rel", + column1="property_id", + column2="tag_id", + ) + tz = fields.Selection( + string="Timezone", + help="This field is used to determine the timezone of the property.", + required=True, + default=lambda self: self.env.user.tz or "UTC", + selection=_tz_get, + ) + area = fields.Float(string="Area") + area_uom_id = fields.Many2one(string="Area UOM", comodel_name="uom.uom") + heating = fields.Selection( + string="Heating", + selection=[ + ("tankless_gas", "Gas (Tankless)"), + ("boiler_gas", "Gas Boiler"), + ("tankless_electric", "Electric (Tankless)"), + ("boiler_electric", "Electric Boiler"), + ("boiler_building", "Building Boiler"), + ], + ) + childs_property_count = fields.Integer( + "Children Count", compute="_compute_childs_property" + ) + team_id = fields.Many2one("pms.team", string="Team") + floors_num = fields.Integer(string="Floor") + unit_floor = fields.Integer(string="Unit Floor") + + @api.depends("property_child_ids") + def _compute_childs_property(self): + for rec in self: + rec.childs_property_count = len(rec.property_child_ids) + + @api.depends("room_ids") + def _compute_room_count(self): + self.room_count = len(self.room_ids) + + def action_view_childs_property_list(self): + action = self.env["ir.actions.actions"]._for_xml_id( + "pms_base.action_pms_property" + ) + action["domain"] = [("id", "in", self.property_child_ids.ids)] + return action + + @api.model + def create(self, vals): + vals.update({"is_property": True}) + return super(PmsProperty, self).create(vals) diff --git a/pms_base/models/pms_room.py b/pms_base/models/pms_room.py new file mode 100644 index 0000000000..cc01f53734 --- /dev/null +++ b/pms_base/models/pms_room.py @@ -0,0 +1,47 @@ +# Copyright 2017 Alexandre Díaz +# Copyright 2017 Dario Lodeiros +# Copyright 2018 Pablo Quesada +# Copyright (c) 2021 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class PmsRoom(models.Model): + _name = "pms.room" + _description = "Property Room" + _order = "sequence, type_id, name" + + 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, + ) + property_id = fields.Many2one( + string="Property", + required=True, + comodel_name="pms.property", + ondelete="restrict", + ) + type_id = fields.Many2one( + string="Room Type", + help="Unique room type for the rooms", + required=True, + comodel_name="pms.room.type", + ondelete="restrict", + ) + capacity = fields.Integer( + string="Capacity", help="The maximum number of people that can occupy a room" + ) + area = fields.Float(string="Area") + _sql_constraints = [ + ( + "room_property_unique", + "unique(name, property_id)", + "You cannot have more 2 rooms with the same name in the same property.", + ) + ] diff --git a/pms_base/models/pms_room_type.py b/pms_base/models/pms_room_type.py new file mode 100644 index 0000000000..6a84c7eeef --- /dev/null +++ b/pms_base/models/pms_room_type.py @@ -0,0 +1,17 @@ +# Copyright 2017 Alexandre Díaz +# Copyright 2017 Dario Lodeiros +# Copyright 2021 Eric Antones +# Copyright (c) 2021 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class PmsRoomType(models.Model): + _name = "pms.room.type" + _description = "Room Type" + + name = fields.Char(string="Name", required=True, translate=True) + sequence = fields.Integer(string="Sequence", default=0) + icon = fields.Char( + string="Website Icon", help="Set Icon name from https://fontawesome.com/" + ) diff --git a/pms_base/models/pms_service.py b/pms_base/models/pms_service.py new file mode 100644 index 0000000000..816e695834 --- /dev/null +++ b/pms_base/models/pms_service.py @@ -0,0 +1,38 @@ +# Copyright 2017 Alexandre Díaz +# Copyright 2017 Dario Lodeiros +# Copyright 2018 Pablo Quesada +# Copyright (c) 2021 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class PmsService(models.Model): + _name = "pms.service" + _description = "Property Service" + + name = fields.Many2one( + string="Service", + help="Service", + required=True, + comodel_name="product.product", + ondelete="restrict", + domain="[('type', '=', 'service')]", + ) + active = fields.Boolean( + string="Active", help="Determines if service 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, + ) + property_id = fields.Many2one( + string="Property", + required=True, + comodel_name="pms.property", + ondelete="restrict", + ) + vendor_id = fields.Many2one( + string="Vendor", required=True, comodel_name="res.partner", ondelete="restrict" + ) diff --git a/pms_base/models/pms_stage.py b/pms_base/models/pms_stage.py new file mode 100644 index 0000000000..848a84d4c4 --- /dev/null +++ b/pms_base/models/pms_stage.py @@ -0,0 +1,50 @@ +# Copyright (c) 2021 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class PMSStage(models.Model): + _name = "pms.stage" + _description = "PMS Stage" + _order = "sequence, name, id" + + def _default_team_ids(self): + default_team_id = self.env.context.get("default_team_id") + return [default_team_id] if default_team_id else None + + name = fields.Char(string="Name", required=True, translate=True) + sequence = fields.Integer("Sequence", default=1) + fold = fields.Boolean( + "Folded in Kanban", + help="This stage is folded in the kanban view when " + "there are no record in that stage to display.", + ) + is_closed = fields.Boolean( + "Is a close stage", help="Services in this stage are considered " "as closed." + ) + is_default = fields.Boolean("Is a default stage", help="Used as default stage") + description = fields.Text(translate=True) + company_id = fields.Many2one( + "res.company", + string="Company", + required=False, + index=True, + default=lambda self: self.env.user.company_id, + ) + team_ids = fields.Many2many( + "pms.team", string="Teams", default=lambda self: self._default_team_ids() + ) + stage_type = fields.Selection([("property", "Property")], "Type", required=True) + custom_color = fields.Char( + "Color Code", default="#FFFFFF", help="Use Hex Code only Ex:-#FFFFFF" + ) + + @api.constrains("custom_color") + def _check_custom_color_hex_code(self): + if ( + self.custom_color + and not self.custom_color.startswith("#") + or len(self.custom_color) != 7 + ): + raise ValidationError(_("Color code should be Hex Code. Ex:-#FFFFFF")) diff --git a/pms_base/models/pms_tag.py b/pms_base/models/pms_tag.py new file mode 100644 index 0000000000..253bd9cd27 --- /dev/null +++ b/pms_base/models/pms_tag.py @@ -0,0 +1,33 @@ +# Copyright 2017 Alexandre Díaz +# Copyright 2017 Dario Lodeiros +# Copyright 2021 Eric Antones +# Copyright (c) 2021 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class PmsTag(models.Model): + _name = "pms.tag" + _description = "PMS Tag" + + name = fields.Char(string="Name", required=True, translate=True) + parent_id = fields.Many2one("pms.tag", string="Parent") + color = fields.Integer("Color Index", default=10) + full_name = fields.Char(string="Full Name", compute="_compute_full_name") + company_id = fields.Many2one( + "res.company", + string="Company", + required=True, + index=True, + default=lambda self: self.env.user.company_id, + help="Company related to this tag", + ) + + _sql_constraints = [("name_uniq", "unique (name)", "Tag name already exists!")] + + def _compute_full_name(self): + for record in self: + if record.parent_id: + record.full_name = record.parent_id.name + "/" + record.name + else: + record.full_name = record.name diff --git a/pms_base/models/pms_team.py b/pms_base/models/pms_team.py new file mode 100644 index 0000000000..bc99781b38 --- /dev/null +++ b/pms_base/models/pms_team.py @@ -0,0 +1,41 @@ +# Copyright (c) 2021 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class PMSTeam(models.Model): + _name = "pms.team" + _description = "PMS Team" + _inherit = ["mail.thread", "mail.activity.mixin"] + + def _default_stages(self): + return self.env["pms.stage"].search([("is_default", "=", True)]) + + def _compute_property_count(self): + property_count = 0 + property_obj = self.env["pms.property"] + for rec in self: + property_count = property_obj.search_count([("team_id", "=", rec.id)]) + rec.property_count = property_count + + name = fields.Char(required=True, translate=True) + description = fields.Text(translate=True) + color = fields.Integer("Color Index") + stage_ids = fields.Many2many("pms.stage", string="Stages", default=_default_stages) + property_ids = fields.One2many("pms.property", "team_id", string="Properties") + property_count = fields.Integer( + compute="_compute_property_count", string="Properties Count" + ) + sequence = fields.Integer( + "Sequence", default=1, help="Used to sort teams. Lower is better." + ) + company_id = fields.Many2one( + "res.company", + string="Company", + required=False, + index=True, + default=lambda self: self.env.user.company_id, + help="Company related to this team", + ) + + _sql_constraints = [("name_uniq", "unique (name)", "Team name already exists!")] diff --git a/pms_base/models/res_company.py b/pms_base/models/res_company.py new file mode 100644 index 0000000000..296c076a79 --- /dev/null +++ b/pms_base/models/res_company.py @@ -0,0 +1,15 @@ +# Copyright 2017 Alexandre Díaz +# Copyright 2017 Dario Lodeiros +# Copyright (c) 2021 Open Source Integrators +# 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_uom = fields.Selection( + [("ft", "Square Foot"), ("m", "Square Meter")], + string="Unit of Measure", + default="m", + ) diff --git a/pms_base/models/res_config_settings.py b/pms_base/models/res_config_settings.py new file mode 100644 index 0000000000..4d77b8310a --- /dev/null +++ b/pms_base/models/res_config_settings.py @@ -0,0 +1,39 @@ +# Copyright 2017 Alexandre Díaz +# Copyright 2017 Dario Lodeiros +# Copyright (c) 2021 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + # Groups + group_pms_show_amenity = fields.Boolean( + string="Show Amenities", implied_group="pms_base.group_pms_show_amenity" + ) + group_pms_show_room = fields.Boolean( + string="Show Rooms", implied_group="pms_base.group_pms_show_room" + ) + group_pms_show_service = fields.Boolean( + string="Show Services", implied_group="pms_base.group_pms_show_service" + ) + group_pms_show_team = fields.Boolean( + string="Show Teams", implied_group="pms_base.group_pms_show_team" + ) + + # Modules + module_pms_account = fields.Boolean(string="Manage Accounting") + module_pms_account_asset = fields.Boolean(string="Manage Assets") + module_pms_contract = fields.Boolean(string="Manage Contracts") + module_pms_crm = fields.Boolean(string="Link a property to a lead") + module_pms_sale = fields.Boolean(string="Manage Reservations") + module_pms_website = fields.Boolean(string="Publish properties") + module_pms_website_sale = fields.Boolean(string="Allow online booking") + module_connector_guesty = fields.Boolean(string="Connect with Guesty") + module_connector_wubook = fields.Boolean(string="Connect with Wubook") + + # Companies + pms_uom = fields.Selection( + string="Unit of Measure", related="company_id.pms_uom", readonly=False + ) diff --git a/pms_base/models/res_partner.py b/pms_base/models/res_partner.py new file mode 100644 index 0000000000..5c17037822 --- /dev/null +++ b/pms_base/models/res_partner.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class ResPartner(models.Model): + _inherit = "res.partner" + + is_property = fields.Boolean(string="Is a Property") diff --git a/pms_base/readme/CONFIGURE.rst b/pms_base/readme/CONFIGURE.rst new file mode 100644 index 0000000000..173b6bdd6a --- /dev/null +++ b/pms_base/readme/CONFIGURE.rst @@ -0,0 +1 @@ +* Go to Properties > Configuration > Settings. diff --git a/pms_base/readme/CONTRIBUTORS.rst b/pms_base/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..ced562e482 --- /dev/null +++ b/pms_base/readme/CONTRIBUTORS.rst @@ -0,0 +1,14 @@ +* Alexandre Díaz +* Pablo Quesada +* Jose Luis Algara +* `Commit [Sun] `: + + * Dario Lodeiros + * Eric Antones + * Sara Lago + * Brais Abeijon + * Miguel Padin + +* `Open Source Integrators `: + + * Maxime Chambreuil diff --git a/pms_base/readme/DESCRIPTION.rst b/pms_base/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..55ee4048c4 --- /dev/null +++ b/pms_base/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +This module is the base module for the property management system (PMS) modules. + +It provides the "Properties" apps with menu, settings, groups and data. diff --git a/pms_base/readme/USAGE.rst b/pms_base/readme/USAGE.rst new file mode 100644 index 0000000000..acb2b8f3d6 --- /dev/null +++ b/pms_base/readme/USAGE.rst @@ -0,0 +1 @@ +To use this module, please read the complete user guide at ``_. diff --git a/pms_base/security/ir.model.access.csv b/pms_base/security/ir.model.access.csv new file mode 100644 index 0000000000..1ca377b777 --- /dev/null +++ b/pms_base/security/ir.model.access.csv @@ -0,0 +1,19 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_pms_amenity_user,access_pms_amenity_user,model_pms_amenity,pms_base.group_pms_user,1,0,0,0 +access_pms_amenity_type_user,access_pms_amenity_type_user,model_pms_amenity_type,pms_base.group_pms_user,1,0,0,0 +access_pms_room_user,access_pms_room_user,model_pms_room,pms_base.group_pms_user,1,0,0,0 +access_pms_tag_user,access_pms_tag_user,model_pms_tag,pms_base.group_pms_user,1,0,0,0 +access_pms_room_type_user,access_pms_room_type_user,model_pms_room_type,pms_base.group_pms_user,1,0,0,0 +access_pms_service_user,access_pms_service_user,model_pms_service,pms_base.group_pms_user,1,0,0,0 +access_pms_stage_use,access_pms_stage,model_pms_stage,pms_base.group_pms_user,1,0,0,0 +access_pms_team_user,access_pms_team,model_pms_team,pms_base.group_pms_user,1,0,0,0 +access_pms_property_user,access_property_user,model_pms_property,pms_base.group_pms_user,1,0,0,0 +access_pms_amenity_manager,access_pms_amenity_manager,model_pms_amenity,pms_base.group_pms_manager,1,1,1,1 +access_pms_amenity_type_manager,access_pms_amenity_type_manager,model_pms_amenity_type,pms_base.group_pms_manager,1,1,1,1 +access_pms_room_manager,access_pms_room_manager,model_pms_room,pms_base.group_pms_manager,1,1,1,1 +access_pms_room_type_manager,access_pms_room_type_manager,model_pms_room_type,pms_base.group_pms_manager,1,1,1,1 +access_pms_service_manager,access_pms_service_manager,model_pms_service,pms_base.group_pms_manager,1,1,1,1 +access_pms_tag_manager,access_pms_tag_manager,model_pms_tag,pms_base.group_pms_manager,1,1,1,1 +access_pms_stage_manager,access_pms_stage,model_pms_stage,pms_base.group_pms_manager,1,1,1,1 +access_pms_team_manager,access_pms_team,model_pms_team,pms_base.group_pms_manager,1,1,1,1 +access_pms_property_manager,access_property_manager,model_pms_property,pms_base.group_pms_manager,1,1,1,1 diff --git a/pms_base/security/ir_rule.xml b/pms_base/security/ir_rule.xml new file mode 100644 index 0000000000..3b06966114 --- /dev/null +++ b/pms_base/security/ir_rule.xml @@ -0,0 +1,13 @@ + + + + + Multi-Company Property Rule + + + ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] + + + diff --git a/pms_base/security/res_groups.xml b/pms_base/security/res_groups.xml new file mode 100644 index 0000000000..c07a32bfc7 --- /dev/null +++ b/pms_base/security/res_groups.xml @@ -0,0 +1,38 @@ + + + + + Property Management / User + + + + + Property Management/ Manager + + + + + + + Show Amenities + + + + + Show Rooms + + + + + Show Services + + + + + Show Teams + + + diff --git a/pms_base/static/description/icon.png b/pms_base/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a81dd64c384b0427f6c7cc4131ebff3e90aee131 GIT binary patch literal 24626 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 literal 0 HcmV?d00001 diff --git a/pms_base/static/description/index.html b/pms_base/static/description/index.html new file mode 100644 index 0000000000..f2429e6669 --- /dev/null +++ b/pms_base/static/description/index.html @@ -0,0 +1,455 @@ + + + + + + +PMS (Property Management System) + + + +
+

PMS (Property Management System)

+ + +

Alpha License: AGPL-3 OCA/pms Translate me on Weblate Try me on Runbot

+

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.

+
+

Important

+

This is an alpha version, the data model and design can change at any time without warning. +Only for development or testing purpose, do not use in production. +More details on development status

+
+

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 smashing 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
    • +
    +
  • +
+
+
+

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_base/views/menu.xml b/pms_base/views/menu.xml new file mode 100644 index 0000000000..ba5b76f2c9 --- /dev/null +++ b/pms_base/views/menu.xml @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pms_base/views/pms_amenity.xml b/pms_base/views/pms_amenity.xml new file mode 100644 index 0000000000..54b4ccd2ff --- /dev/null +++ b/pms_base/views/pms_amenity.xml @@ -0,0 +1,66 @@ + + + + pms.amenity.form + pms.amenity + +
+
+ +
+

+

+ + + + + + + + + + + + + + + + + + + + pms.amenity.search + pms.amenity + + + + + + + + + + + pms.amenity.tree + pms.amenity + + + + + + + + + + + Amenities + pms.amenity + tree,form + + + diff --git a/pms_base/views/pms_amenity_type.xml b/pms_base/views/pms_amenity_type.xml new file mode 100644 index 0000000000..f17be73df7 --- /dev/null +++ b/pms_base/views/pms_amenity_type.xml @@ -0,0 +1,44 @@ + + + + + pms.amenity.type.form + pms.amenity.type + +
+
+ +
+