diff --git a/pms/__manifest__.py b/pms/__manifest__.py index d56170614..fed5c2ff8 100644 --- a/pms/__manifest__.py +++ b/pms/__manifest__.py @@ -14,6 +14,7 @@ "installable": True, "depends": [ "base", + "base_automation", "mail", # "account_payment_return", # "email_template_qweb", @@ -32,6 +33,9 @@ "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_data.xml", "data/traveller_report_paperformat.xml", "report/pms_folio.xml", @@ -77,6 +81,7 @@ "views/res_company_views.xml", "views/traveller_report_template.xml", "wizards/wizard_split_join_swap_reservation.xml", + "views/pms_automated_mails_views.xml", "wizards/wizard_massive_changes.xml", "wizards/wizard_advanced_filters.xml", ], 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 000000000..31ab04cc0 --- /dev/null +++ b/pms/data/pms_cancelled_reservation_email_template.xml @@ -0,0 +1,160 @@ + + + + + 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 successfully canceled. +
+
+
+ + +
+
+ +
+
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 000000000..aedc5d3fc --- /dev/null +++ b/pms/data/pms_confirmed_reservation_email_template.xml @@ -0,0 +1,260 @@ + + + + + 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.checkin} At ${object.arrival_hour}
+
To ${object.checkout} At ${object.departure_hour}
+
TZ ${object.pms_property_id.tz}
+
+
+
+
+
+
Room: ${object.room_type_id.name}
+
+
+
+
+
+
Price: ${object.price_room_services_set} ${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_data.xml b/pms/data/pms_data.xml index edfae1c26..57bd9e782 100644 --- a/pms/data/pms_data.xml +++ b/pms/data/pms_data.xml @@ -135,7 +135,40 @@ if (permit_first_letter.upper() in ['X','Y']) and id_number.name[1:8].isdigit() else: failed = True - + + + + Confirmed Reservation + False + + creation + + in_act + + + Modified Reservation + False + + write + + in_act + + + Cancelled Reservation + False + + cancel + + in_act 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 000000000..9ca3a0942 --- /dev/null +++ b/pms/data/pms_modified_reservation_email_template.xml @@ -0,0 +1,262 @@ + + + + + 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.checkin} At ${object.arrival_hour}
+
To ${object.checkout} At ${object.departure_hour}
+
TZ ${object.pms_property_id.tz}
+
+
+
+
+
+
Room: ${object.room_type_id.name}
+
+
+
+
+
+
Price: ${object.price_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/models/__init__.py b/pms/models/__init__.py index 5bcb034f5..8b246ec75 100644 --- a/pms/models/__init__.py +++ b/pms/models/__init__.py @@ -31,7 +31,7 @@ from . import product_pricelist_item from . import res_partner from . import pms_sale_channel -# from . import mail_compose_message +from . import mail_compose_message from . import pms_room_type_class from . import pms_room_closure_reason from . import pms_service_line @@ -46,3 +46,4 @@ from . import account_bank_statement from . import account_journal from . import pms_availability from . import res_partner_id_number +from . import pms_automated_mails diff --git a/pms/models/mail_compose_message.py b/pms/models/mail_compose_message.py index 62bbaad3b..e76de0ec6 100644 --- a/pms/models/mail_compose_message.py +++ b/pms/models/mail_compose_message.py @@ -1,24 +1,43 @@ # Copyright 2017 Alexandre Díaz # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import models +from odoo import api, models class MailComposeMessage(models.TransientModel): _inherit = "mail.compose.message" + @api.model + def default_get(self, fields): + res = super(MailComposeMessage, self).default_get(fields) + template = self.env["mail.template"].browse(self._context.get("template_id")) + res.update( + { + "composition_mode": "comment", + "attachment_ids": False, + "template_id": template.id, + } + ) + return res + def send_mail(self, auto_commit=False): - if ( - self._context.get("default_model") == "pms.folio" - and self._context.get("default_res_id") - and self._context.get("mark_so_as_sent") - ): - # TODO: WorkFlow Mails - folio = self.env["pms.folio"].browse([self._context["default_res_id"]]) - if folio: - cmds = [ - (1, lid, {"to_send": False}) for lid in folio.reservation_ids.ids - ] - if any(cmds): - folio.reservation_ids = cmds - return super(MailComposeMessage, self).send_mail(auto_commit=auto_commit) + # if ( + # self._context.get("default_model") == "pms.folio" + # and self._context.get("default_res_id") + # and self._context.get("mark_so_as_sent") + # ): + # # TODO: WorkFlow Mails + # folio = self.env["pms.folio"].browse([self._context["default_res_id"]]) + # if folio: + # cmds = [ + # (1, lid, {"to_send": False}) for lid in folio.reservation_ids.ids + # ] + # if any(cmds): + # folio.reservation_ids = cmds + res = super(MailComposeMessage, self).send_mail(auto_commit=auto_commit) + if self._context.get("record_id"): + reservation = self.env["pms.reservation"].search( + [("id", "=", self._context.get("record_id"))] + ) + reservation.is_mail_send = True + return res diff --git a/pms/models/pms_automated_mails.py b/pms/models/pms_automated_mails.py new file mode 100644 index 000000000..175a8b661 --- /dev/null +++ b/pms/models/pms_automated_mails.py @@ -0,0 +1,367 @@ +from odoo import _, api, fields, models +from odoo.exceptions import UserError + + +class PmsAutomatedMails(models.Model): + _name = "pms.automated.mails" + _description = "Automatic Mails" + + name = fields.Char(string="Name", help="Name of the automated mail.", required=True) + + pms_property_ids = fields.Many2many( + string="Property", + help="Properties with access to the element;" + " if not set, all properties can access", + comodel_name="pms.property", + ) + + automated_actions_id = fields.Many2one( + string="Automated Actions", + help="automated action that is created when creating automated emails ", + comodel_name="base.automation", + ) + + time = fields.Integer(string="Time", help="Amount of time") + + time_type = fields.Selection( + string="Time Range", + help="Type of date range", + selection=[ + ("minutes", "Minutes"), + ("hour", "Hour"), + ("day", "Days"), + ("month", "Months"), + ], + default="day", + ) + template_id = fields.Many2one( + string="Template", + help="The template that will be sent by email", + comodel_name="mail.template", + required=True, + ) + + action = fields.Selection( + string="Action", + help="The action that will cause the email to be sent ", + selection=[ + ("creation", "Reservation creation"), + ("write", "Reservation modification"), + ("cancel", "Reservation cancellation"), + ("checkin", "Checkin"), + ("checkout", "Checkout"), + ("payment", "Payment"), + ("invoice", "Invoice"), + ], + default="creation", + required=True, + ) + + moment = fields.Selection( + string="Moment", + help="Moment in relation to the action in which the email will be sent", + selection=[ + ("before", "Before"), + ("after", "After"), + ("in_act", "In the act"), + ], + default="before", + ) + + active = fields.Boolean( + string="Active", help="Indicates if the automated mail is active", default=True + ) + + @api.model + def create(self, vals): + name = vals.get("name") + action = vals.get("action") + time = vals.get("time") + date_range_type = vals.get("time_type") + template_id = vals.get("template_id") + active = vals.get("active") + moment = vals.get("moment") + properties = vals.get("pms_property_ids") + is_create = True + if action in ("creation", "write", "cancel", "invoice") and moment == "before": + raise UserError(_("The moment for this action cannot be 'Before'")) + dict_val = self._prepare_automated_actions_id( + action, time, moment, properties, is_create + ) + action_server_vals = { + "name": name, + "state": "email", + "usage": "ir_cron", + "model_id": dict_val["model_id"], + } + action_server = self.env["ir.actions.server"].create(action_server_vals) + automated_actions_vals = { + "active": active, + "action_server_id": action_server.id, + "trigger": dict_val["trigger"], + "filter_domain": dict_val["filter_domain"], + "filter_pre_domain": dict_val["filter_pre_domain"], + "trg_date_range": dict_val["time"], + "trg_date_range_type": date_range_type, + "template_id": template_id, + } + model_field = dict_val["model_field"] + if model_field: + automated_actions_vals.update( + { + "trg_date_id": dict_val["model_field"].id, + } + ) + trigger_field = dict_val["trigger_fields"] + if trigger_field: + automated_actions_vals.update( + { + "trigger_field_ids": dict_val["trigger_fields"].ids, + } + ) + automated_action = self.env["base.automation"].create(automated_actions_vals) + vals.update({"automated_actions_id": automated_action.id}) + return super(PmsAutomatedMails, self).create(vals) + + def write(self, vals): + result = super(PmsAutomatedMails, self).write(vals) + is_create = False + if ( + self.action in ("creation", "write", "cancel", "invoice") + and self.moment == "before" + ): + raise UserError(_("The moment for this action cannot be 'Before'")) + dict_val = self._prepare_automated_actions_id( + self.action, self.time, self.moment, self.pms_property_ids, is_create + ) + automated_actions_id = self.automated_actions_id + action_server = automated_actions_id.action_server_id + action_server_vals = { + "name": self.name, + "state": "email", + "usage": "ir_cron", + "model_id": dict_val["model_id"], + } + action_server.write(action_server_vals) + automated_actions_vals = { + "active": self.active, + "action_server_id": action_server.id, + "trigger": dict_val["trigger"], + "filter_domain": dict_val["filter_domain"], + "filter_pre_domain": dict_val["filter_pre_domain"], + "trg_date_range": dict_val["time"], + "trg_date_range_type": self.time_type, + "template_id": self.template_id, + } + model_field = dict_val["model_field"] + if model_field: + automated_actions_vals.update( + { + "trg_date_id": dict_val["model_field"].id, + } + ) + trigger_field = dict_val["trigger_fields"] + if trigger_field: + automated_actions_vals.update( + { + "trigger_field_ids": dict_val["trigger_fields"].ids, + } + ) + automated_actions_id.write(automated_actions_vals) + vals.update({"automated_actions_id": automated_actions_id.id}) + return result + + def unlink(self): + automated_actions_id = self.automated_actions_id + action_server = automated_actions_id.action_server_id + automated_actions_id.unlink() + action_server.unlink() + return super(PmsAutomatedMails, self).unlink() + + @api.model + def _get_auto_action_fields_in_creation_action(self, moment, time): + model_field = False + model_id = self.env["ir.model"].search([("model", "=", "pms.reservation")]).id + if moment == "in_act": + trigger = "on_create" + time = 0 + else: + trigger = "on_time" + model_field = self.env["ir.model.fields"].search( + [("model", "=", "pms.reservation"), ("name", "=", "create_date")] + ) + result = { + "model_id": model_id, + "trigger": trigger, + "model_field": model_field, + "time": time, + } + return result + + @api.model + def _get_auto_action_fields_in_write_or_cancel_action(self, moment, time): + model_field = False + model_id = self.env["ir.model"].search([("model", "=", "pms.reservation")]).id + if moment == "in_act": + trigger = "on_write" + time = 0 + else: + trigger = "on_time" + model_field = self.env["ir.model.fields"].search( + [("model", "=", "pms.reservation"), ("name", "=", "write_date")] + ) + result = { + "model_id": model_id, + "trigger": trigger, + "model_field": model_field, + "time": time, + } + return result + + @api.model + def _get_auto_action_fields_in_checkin_action(self, moment, time): + model_id = self.env["ir.model"].search([("model", "=", "pms.reservation")]).id + trigger = "on_time" + model_field = self.env["ir.model.fields"].search( + [("model", "=", "pms.reservation"), ("name", "=", "checkin")] + ) + if moment == "before": + time = time * (-1) + if moment == "in_act": + trigger = "on_write" + time = 0 + result = { + "model_id": model_id, + "trigger": trigger, + "model_field": model_field, + "time": time, + } + return result + + @api.model + def _get_auto_action_fields_in_checkout_action(self, moment, time): + model_id = self.env["ir.model"].search([("model", "=", "pms.reservation")]).id + trigger = "on_time" + model_field = self.env["ir.model.fields"].search( + [("model", "=", "pms.reservation"), ("name", "=", "checkout")] + ) + if moment == "before": + time = time * (-1) + if moment == "in_act": + trigger = "on_write" + time = 0 + result = { + "model_id": model_id, + "trigger": trigger, + "model_field": model_field, + "time": time, + } + return result + + @api.model + def _get_auto_action_fields_in_payment_action(self, moment, time): + model_field = False + model_id = ( + self.env["ir.model"] + .search([("model", "=", "account.payment"), ("transient", "=", False)]) + .id + ) + if moment == "in_act": + trigger = "on_create" + time = 0 + else: + trigger = "on_time" + model_field = self.env["ir.model.fields"].search( + [("model", "=", "account.payment"), ("name", "=", "date")] + ) + if moment == "before": + time = time * (-1) + result = { + "model_id": model_id, + "trigger": trigger, + "model_field": model_field, + "time": time, + } + return result + + @api.model + def _get_auto_action_fields_in_invoice_action(self, moment, time): + trigger = False + model_id = self.env["ir.model"].search([("model", "=", "account.move")]).id + if moment == "in_act": + trigger = "on_create" + time = 0 + result = { + "model_id": model_id, + "time": time, + "trigger": trigger, + "model_field": False, + } + return result + + def _prepare_automated_actions_id( + self, action, time, moment, properties, is_create + ): + filter_domain = [] + filter_pre_domain = [] + trigger_fields = False + dict_val = {} + if action == "creation": + dict_val = self._get_auto_action_fields_in_creation_action(moment, time) + elif action == "write" or action == "cancel": + dict_val = self._get_auto_action_fields_in_write_or_cancel_action( + moment, time + ) + if action == "cancel": + filter_domain = [ + ("state", "=", "cancelled"), + ] + elif action == "checkin": + dict_val = self._get_auto_action_fields_in_checkin_action(moment, time) + if moment == "in_act": + trigger_fields = self.env["ir.model.fields"].search( + [("model", "=", "pms.reservation"), ("name", "=", "state")] + ) + filter_pre_domain = [("state", "=", "confirm")] + filter_domain = [ + ("state", "=", "onboard"), + ] + elif action == "checkout": + dict_val = self._get_auto_action_fields_in_checkout_action(moment, time) + if moment == "in_act": + trigger_fields = self.env["ir.model.fields"].search( + [("model", "=", "pms.reservation"), ("name", "=", "state")] + ) + filter_pre_domain = [("state", "=", "onboard")] + filter_domain = [ + ("state", "=", "out"), + ] + elif action == "payment": + dict_val = self._get_auto_action_fields_in_payment_action(moment, time) + elif action == "invoice": + dict_val = self._get_auto_action_fields_in_invoice_action(moment, time) + filter_domain = [ + ("folio_ids", "!=", False), + ] + pms_property_ids = self._get_pms_property_ids(properties, is_create) + if pms_property_ids: + filter_domain.append(("pms_property_id", "in", pms_property_ids)) + result = { + "trigger": dict_val["trigger"], + "model_field": dict_val["model_field"], + "trigger_fields": trigger_fields, + "filter_pre_domain": filter_pre_domain, + "filter_domain": filter_domain, + "time": dict_val["time"], + "model_id": dict_val["model_id"], + } + return result + + def _get_pms_property_ids(self, properties, is_create): + pms_property_ids = [] + if is_create: + pms_property_ids = properties[0][2] + else: + for pms_property in properties: + pms_property_ids.append(pms_property.id) + return pms_property_ids diff --git a/pms/models/pms_property.py b/pms/models/pms_property.py index 2656f4239..a524ed083 100644 --- a/pms/models/pms_property.py +++ b/pms/models/pms_property.py @@ -115,6 +115,39 @@ class PmsProperty(models.Model): 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 Template", + help="Confirmation email template", + comodel_name="mail.template", + default=lambda self: self.env["mail.template"] + .search([("name", "=", "Confirmed Reservation")]) + .id, + ) + + property_modified_template = fields.Many2one( + string="Modification Template", + help="Modification email template", + comodel_name="mail.template", + default=lambda self: self.env["mail.template"] + .search([("name", "=", "Modified Reservation")]) + .id, + ) + + property_canceled_template = fields.Many2one( + string="Cancellation Template", + help="Cancellation email template", + comodel_name="mail.template", + default=lambda self: self.env["mail.template"] + .search([("name", "=", "Cancelled Reservation")]) + .id, + ) + @api.depends_context( "checkin", "checkout", diff --git a/pms/models/pms_reservation.py b/pms/models/pms_reservation.py index c2b3c2b63..3e0715b33 100644 --- a/pms/models/pms_reservation.py +++ b/pms/models/pms_reservation.py @@ -644,6 +644,19 @@ class PmsReservation(models.Model): add_possible_customer = fields.Boolean(string="Add possible Customer") + is_mail_send = fields.Boolean(string="Mail Sent", default=False) + + is_modified_reservation = fields.Boolean( + string="Is A Modified Reservation", + compute="_compute_is_modified_reservation", + readonly=False, + store=True, + ) + + lang = fields.Many2one( + string="Language", comodel_name="res.lang", compute="_compute_lang" + ) + def _compute_date_order(self): for record in self: record.date_order = datetime.datetime.today() @@ -1428,6 +1441,25 @@ class PmsReservation(models.Model): for record in self: self.env["pms.folio"]._apply_is_possible_existing_customer_id(record) + @api.depends("checkin", "checkout") + def _compute_is_modified_reservation(self): + for record in self: + if record.state in "draft": + record.is_modified_reservation = False + elif record.state in ("confirm", "onboard") and record.is_mail_send: + record.is_modified_reservation = True + record.is_mail_send = False + else: + record.is_modified_reservation = 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() + def _search_allowed_checkin(self, operator, value): if operator not in ("=",): raise UserError( @@ -1666,6 +1698,54 @@ class PmsReservation(models.Model): }, } + def action_open_mail_composer(self): + self.ensure_one() + template = False + if ( + not self.is_mail_send + and not self.is_modified_reservation + and self.state not in "cancel" + ): + template = self.env.ref( + "pms.confirmed_reservation_email", raise_if_not_found=False + ) + elif ( + not self.is_mail_send + and self.is_modified_reservation + and self.state not in "cancel" + ): + template = self.env.ref( + "pms.modified_reservation_email", raise_if_not_found=False + ) + elif not self.is_mail_send and self.state in "cancel": + template = self.env.ref( + "pms.cancelled_reservation_email", raise_if_not_found=False + ) + compose_form = self.env.ref( + "mail.email_compose_message_wizard_form", raise_if_not_found=False + ) + ctx = dict( + model="pms.reservation", + default_res_model="pms.reservation", + default_res_id=self.id, + template_id=template and template.id or False, + composition_mode="comment", + partner_ids=[self.partner_id.id], + force_email=True, + record_id=self.id, + ) + return { + "name": _("Send Confirmed Reservation 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, + } + @api.model def name_search(self, name="", args=None, operator="ilike", limit=100): if args is None: @@ -1811,6 +1891,7 @@ class PmsReservation(models.Model): else: record.state = "cancel" record.folio_id._compute_amount() + record.is_mail_send = False def action_assign(self): for record in self: diff --git a/pms/security/ir.model.access.csv b/pms/security/ir.model.access.csv index 544f75411..f536bb9a2 100644 --- a/pms/security/ir.model.access.csv +++ b/pms/security/ir.model.access.csv @@ -60,3 +60,4 @@ user_access_wizard_payment_folio,user_access_wizard_payment_folio,model_wizard_p 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 diff --git a/pms/tests/__init__.py b/pms/tests/__init__.py index 4f21325fd..43e0a2cbe 100644 --- a/pms/tests/__init__.py +++ b/pms/tests/__init__.py @@ -38,3 +38,4 @@ 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 diff --git a/pms/tests/test_automated_mails.py b/pms/tests/test_automated_mails.py new file mode 100644 index 000000000..8f60d2abd --- /dev/null +++ b/pms/tests/test_automated_mails.py @@ -0,0 +1,618 @@ +from odoo.exceptions import UserError + +from .common import TestPms + + +class TestPmsAutomatedMails(TestPms): + def setUp(self): + super().setUp() + self.template = self.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_before_in_checkout(self): + """ + Check that when creating an automated mail with parameters + action = 'checkout' 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": "checkout", + "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_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', '=', 'out'), ('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_reservation.py b/pms/tests/test_pms_reservation.py index 21706c792..c7c48f119 100644 --- a/pms/tests/test_pms_reservation.py +++ b/pms/tests/test_pms_reservation.py @@ -3424,3 +3424,77 @@ class TestPmsReservations(TestPms): partner.id, "The partner was not added to the reservation ", ) + + def test_is_modified_reservation(self): + """ + Checked that the is_modified_reservation field is correctly set + to True when the checkin or checkout fields are modified in a + reservation. + ---------------------- + A reservation is created. The checkin and checkout fields of + the reservation are modified. The state of the boolean + is_mail_send is changed to True so that the compute of + the is_modified_reservation field is activated correctly + and it is verified that the state of this field is True. + """ + # ARRANGE + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=2) + 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, + } + + reservation = self.env["pms.reservation"].create(reservation_vals) + + # ACT + writed_checkin = fields.date.today() + datetime.timedelta(days=4) + writed_checkout = fields.date.today() + datetime.timedelta(days=6) + reservation.is_mail_send = True + reservation.update( + { + "checkin": writed_checkin, + "checkout": writed_checkout, + } + ) + + # ASSERT + self.assertTrue( + reservation.is_modified_reservation, + "is_modified_reservation field should be True ", + ) + + def test_is_not_modified_reservation(self): + """ + Checked that the is_modified_reservation field is correctly set + to False when the reservation is modified but not the checkin + or checkout fields. + ---------------------- + A reservation is created. The adults, arrival_hour and departure_hours + fields of the reservation are modified.The it is verified that the state + of this field is False. + """ + # ARRANGE + checkin = fields.date.today() + checkout = fields.date.today() + datetime.timedelta(days=2) + 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, + } + + reservation = self.env["pms.reservation"].create(reservation_vals) + reservation.update( + {"adults": 1, "arrival_hour": "18:00", "departure_hour": "08:00"} + ) + + # ASSERT + self.assertFalse( + reservation.is_modified_reservation, + "is_modified_reservation field should be False ", + ) diff --git a/pms/views/pms_automated_mails_views.xml b/pms/views/pms_automated_mails_views.xml new file mode 100644 index 000000000..4f49aa5d9 --- /dev/null +++ b/pms/views/pms_automated_mails_views.xml @@ -0,0 +1,73 @@ + + + + pms.automated_mails_view_form + pms.automated.mails + +
+ +
+
+
+
+ + + + + + +
+
+ + + + + +
+
+
+
+
+
+ + pms.automated.mails.tree + pms.automated.mails + + + + + + + + + + + + + Automated Mails + pms.automated.mails + + tree,form + + +
diff --git a/pms/views/pms_property_views.xml b/pms/views/pms_property_views.xml index 4ac88fb28..f62b968f8 100644 --- a/pms/views/pms_property_views.xml +++ b/pms/views/pms_property_views.xml @@ -85,6 +85,20 @@ + + + + + + + + + + + diff --git a/pms/views/pms_reservation_views.xml b/pms/views/pms_reservation_views.xml index d9c3169ef..233b62735 100644 --- a/pms/views/pms_reservation_views.xml +++ b/pms/views/pms_reservation_views.xml @@ -43,7 +43,27 @@ states="onboard,departure_delayed" type="object" /> +