[MIG] fsm_visit_confirmation to 18.0
This commit is contained in:
parent
5793351bbe
commit
ca493863cb
14 changed files with 1284 additions and 0 deletions
2
fsm_visit_confirmation/__init__.py
Normal file
2
fsm_visit_confirmation/__init__.py
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
from . import controllers
|
||||
from . import models
|
||||
54
fsm_visit_confirmation/__manifest__.py
Normal file
54
fsm_visit_confirmation/__manifest__.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
#
|
||||
# Bemade Inc.
|
||||
#
|
||||
# Copyright (C) 2023-June Bemade Inc. (<https://www.bemade.org>).
|
||||
# Author: Marc Durepos (Contact : marc@bemade.org)
|
||||
#
|
||||
# This program is under the terms of the GNU Lesser General Public License,
|
||||
# version 3.
|
||||
#
|
||||
# For full license details, see https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
{
|
||||
"name": "FSM Visit Confirmation",
|
||||
"version": "18.0.0.1.0",
|
||||
"summary": "Enable client feedback workflow for field service tasks",
|
||||
"description": """
|
||||
This module enhances the field service management workflow by leveraging Odoo's
|
||||
built-in rating system for client task confirmations. Key features include:
|
||||
|
||||
* Automated rating requests to work order contacts when tasks reach specific stages
|
||||
* Configurable stages with rating email templates
|
||||
* Client-facing rating interface with direct links in emails
|
||||
* Support for task approval or change requests through ratings
|
||||
* Integration with existing work order contacts from bemade_fsm
|
||||
* Automatic task state updates based on client feedback
|
||||
* Real-time feedback through website messages and notifications
|
||||
* Chatter integration for client comments
|
||||
|
||||
The module helps streamline communication between field service teams and clients
|
||||
by providing a clear feedback workflow and maintaining a record of all client
|
||||
interactions and approvals through Odoo's rating system.
|
||||
""",
|
||||
"category": "Services/Field Service",
|
||||
"author": "Bemade Inc.",
|
||||
"website": "http://www.bemade.org",
|
||||
"license": "LGPL-3",
|
||||
"depends": ["industry_fsm", "bemade_fsm", "rating", "http_routing"],
|
||||
"data": [
|
||||
"data/mail_templates.xml",
|
||||
"views/project_portal_project_task_templates.xml",
|
||||
"views/project_task_type_views.xml",
|
||||
],
|
||||
"installable": True,
|
||||
"auto_install": False,
|
||||
"application": False,
|
||||
}
|
||||
1
fsm_visit_confirmation/controllers/__init__.py
Normal file
1
fsm_visit_confirmation/controllers/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from . import main
|
||||
270
fsm_visit_confirmation/controllers/main.py
Normal file
270
fsm_visit_confirmation/controllers/main.py
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
import werkzeug
|
||||
|
||||
from odoo import http, _
|
||||
from odoo.http import request
|
||||
from odoo.addons.base.models.ir_qweb import keep_query
|
||||
from odoo.addons.portal.controllers.portal import CustomerPortal
|
||||
from odoo.exceptions import AccessError, MissingError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CustomerPortalExtended(CustomerPortal):
|
||||
"""Extend the customer portal controller to handle FSM visit confirmations."""
|
||||
|
||||
@http.route("/my/task/<int:task_id>", type="http", auth="public", website=True)
|
||||
def portal_my_task(self, task_id, access_token=None, **kw):
|
||||
"""Override to allow access with token for public users."""
|
||||
try:
|
||||
task = request.env["project.task"].browse(task_id)
|
||||
task.check_access_rights("read")
|
||||
task.check_access_rights("write")
|
||||
task.check_access_rule("read")
|
||||
task.check_access_rule("write")
|
||||
except AccessError:
|
||||
task = None
|
||||
values = self._get_portal_values(task, access_token=access_token, **kw)
|
||||
if not task:
|
||||
task = values.get("task", False)
|
||||
if not task:
|
||||
return self._render_error_page(
|
||||
_("Task not found or invalid token"), task=None
|
||||
)
|
||||
|
||||
# Pass the task to the original method
|
||||
result = super(CustomerPortalExtended, self).portal_my_task(
|
||||
task_id, access_token=access_token, **kw
|
||||
)
|
||||
|
||||
# If the result is a Response object (like a redirect), return it directly
|
||||
if isinstance(result, werkzeug.wrappers.Response):
|
||||
return result
|
||||
|
||||
# Otherwise, it's a rendered template, so we need to update the qcontext
|
||||
if hasattr(result, "qcontext"):
|
||||
result.qcontext.update(values)
|
||||
|
||||
return result
|
||||
|
||||
@http.route(
|
||||
"/my/fsm_confirmation/<string:action>",
|
||||
type="http",
|
||||
auth="public",
|
||||
website=True,
|
||||
)
|
||||
def fsm_confirmation_action(self, action, **kwargs):
|
||||
"""Custom landing page for FSM visit confirmations.
|
||||
|
||||
Args:
|
||||
action: Either 'approve' or 'change'
|
||||
Kwargs:
|
||||
access_token: The access token for the task
|
||||
"""
|
||||
values = self._get_portal_values(**kwargs)
|
||||
task = values.get("task", False)
|
||||
if not task:
|
||||
return self._render_error_page(
|
||||
_("Task not found or invalid token"), task=None
|
||||
)
|
||||
# Store the language in kwargs for use in redirects
|
||||
lang = values.get("lang")
|
||||
|
||||
if action not in ("approve", "change"):
|
||||
raise werkzeug.exceptions.BadRequest(_("Invalid action"))
|
||||
|
||||
# If it's an approval, update the task state directly
|
||||
if action == "approve":
|
||||
try:
|
||||
# Update task state to approved
|
||||
task.write({"state": "03_approved"})
|
||||
|
||||
# Add a message to the chatter
|
||||
task.message_post(
|
||||
body=_("Visit approved by customer"),
|
||||
message_type="comment",
|
||||
subtype_xmlid="mail.mt_note",
|
||||
)
|
||||
|
||||
# Always redirect to the task portal page with success message
|
||||
redirect_url = "/my/task/%s?%s" % (
|
||||
task.id,
|
||||
keep_query(
|
||||
"access_token",
|
||||
visit_confirmation_status="approved",
|
||||
lang=lang,
|
||||
),
|
||||
)
|
||||
return request.redirect(redirect_url)
|
||||
except Exception as e:
|
||||
_logger.exception(_("Error approving task: %s"), e)
|
||||
return self._render_error_page(
|
||||
_("Error approving task: %s") % str(e), task=task
|
||||
)
|
||||
|
||||
# For change requests, redirect to the task portal page with change request form
|
||||
redirect_url = "/my/task/%s?%s" % (
|
||||
task.id,
|
||||
keep_query(
|
||||
"access_token",
|
||||
visit_confirmation_status="change",
|
||||
lang=lang,
|
||||
),
|
||||
)
|
||||
return request.redirect(redirect_url)
|
||||
|
||||
@http.route(
|
||||
"/my/fsm_confirmation/submit_change",
|
||||
type="http",
|
||||
auth="public",
|
||||
methods=["post"],
|
||||
website=True,
|
||||
csrf=True,
|
||||
)
|
||||
def fsm_confirmation_submit_change(self, **kwargs):
|
||||
"""Handle the submission of change request form."""
|
||||
values = self._get_portal_values(**kwargs)
|
||||
|
||||
task = values.get("task", False)
|
||||
lang = values.get("lang")
|
||||
feedback = kwargs.get("feedback", "")
|
||||
if not task:
|
||||
return self._render_error_page(
|
||||
_("Task not found or invalid token"), task=None
|
||||
)
|
||||
if not feedback:
|
||||
return self._render_error_page(
|
||||
_("No feedback provided. Please try again!"), task=task
|
||||
)
|
||||
|
||||
try:
|
||||
# Update task state to changes requested
|
||||
task.write({"state": "02_changes_requested"})
|
||||
|
||||
# Add the feedback as a message to the chatter
|
||||
task.message_post(
|
||||
body=_("Changes requested by customer: %s") % feedback,
|
||||
message_type="comment",
|
||||
subtype_xmlid="mail.mt_comment",
|
||||
)
|
||||
|
||||
# Always redirect to the task portal page with success message
|
||||
redirect_url = "/my/task/%s?%s" % (
|
||||
task.id,
|
||||
keep_query(
|
||||
access_token=kwargs.get("access_token"),
|
||||
visit_confirmation_status="change_submitted",
|
||||
lang=lang,
|
||||
),
|
||||
)
|
||||
return request.redirect(redirect_url)
|
||||
except Exception as e:
|
||||
_logger.exception(_("Error submitting change request: %s"), e)
|
||||
return self._render_error_page(
|
||||
_("Error submitting change request: %s") % str(e), task=task
|
||||
)
|
||||
|
||||
def _get_task_by_token(self, token):
|
||||
"""Get a task by its access token."""
|
||||
# First try to find the task directly by access token
|
||||
task = (
|
||||
request.env["project.task"]
|
||||
.sudo()
|
||||
.search([("access_token", "=", token)], limit=1)
|
||||
)
|
||||
if task:
|
||||
return task
|
||||
|
||||
# If not found, try to find it through the rating token
|
||||
# This is for backward compatibility with existing tokens in emails
|
||||
rating = (
|
||||
request.env["rating.rating"]
|
||||
.sudo()
|
||||
.search([("access_token", "=", token)], limit=1)
|
||||
)
|
||||
if rating and rating.res_model == "project.task":
|
||||
task = request.env["project.task"].sudo().browse(rating.res_id)
|
||||
if task.exists():
|
||||
return task
|
||||
|
||||
return None
|
||||
|
||||
def _render_error_page(self, error_message, task=None, **kwargs):
|
||||
"""Render an error page."""
|
||||
values = {
|
||||
"status_code": "400",
|
||||
"status_message": error_message,
|
||||
"lang": self._get_lang(task=task, lang=kwargs.get("lang")),
|
||||
}
|
||||
|
||||
return request.render("http_routing.http_error", values)
|
||||
|
||||
def _get_portal_values(self, task=None, **kwargs):
|
||||
"""Get common values for portal templates.
|
||||
|
||||
Args:
|
||||
task: The task record if already loaded
|
||||
**kwargs: Keyword arguments from the request
|
||||
|
||||
Returns:
|
||||
Dictionary with values for template rendering
|
||||
"""
|
||||
values = {"page_name": "task"}
|
||||
if not task:
|
||||
access_token = kwargs.get("access_token")
|
||||
if access_token:
|
||||
task = self._get_task_by_token(access_token)
|
||||
if task:
|
||||
values["task"] = task
|
||||
|
||||
# Set language and update values dictionary
|
||||
values["lang"] = self._get_lang(task=task, lang=kwargs.get("lang"))
|
||||
|
||||
# Add any additional parameters from kwargs that might be needed in templates
|
||||
for key in ["visit_confirmation_status", "error", "warning", "success"]:
|
||||
if kwargs.get(key):
|
||||
values[key] = kwargs.get(key)
|
||||
|
||||
return values
|
||||
|
||||
def _get_lang(self, task=None, lang=None):
|
||||
"""Helper method to set the language in the request environment.
|
||||
|
||||
Args:
|
||||
task: The task record (optional)
|
||||
lang: Language code (e.g., 'en_US', 'fr_FR') (optional)
|
||||
|
||||
Returns:
|
||||
The language code that was set, or None if no language was set
|
||||
"""
|
||||
# If lang is not provided, try to get it from the task
|
||||
if not lang and task:
|
||||
lang_partner = (
|
||||
task.work_order_contacts
|
||||
and task.work_order_contacts[0]
|
||||
or task.partner_id
|
||||
)
|
||||
if lang_partner and lang_partner.lang:
|
||||
lang = lang_partner.lang
|
||||
|
||||
if not lang:
|
||||
return None
|
||||
|
||||
# Get the language record
|
||||
lang_code = lang.replace("_", "-")
|
||||
lang_id = (
|
||||
request.env["res.lang"]
|
||||
.sudo()
|
||||
.search(["|", ("code", "=", lang_code), ("code", "=", lang)], limit=1)
|
||||
)
|
||||
|
||||
if lang_id:
|
||||
# Set the context language
|
||||
request.env = request.env(
|
||||
context=dict(request.env.context, lang=lang_id.code)
|
||||
)
|
||||
return lang_id.code
|
||||
|
||||
return None
|
||||
93
fsm_visit_confirmation/data/mail_templates.xml
Normal file
93
fsm_visit_confirmation/data/mail_templates.xml
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="fsm_visit_confirmation_email_template" model="mail.template">
|
||||
<field name="name">FSM Visit Confirmation</field>
|
||||
<field name="model_id" ref="project.model_project_task"/>
|
||||
<field name="subject">Service Visit Confirmation: {{ object.name }}</field>
|
||||
<field name="email_from">{{ user.email_formatted }}</field>
|
||||
<field name="partner_to">{{ object.work_order_contacts and object.work_order_contacts[0].id or object.partner_id.id }}</field>
|
||||
<field name="description">Send a visit confirmation email to the customer</field>
|
||||
<field name="body_html" type="html">
|
||||
<div>
|
||||
<div style="margin-bottom: 16px;">
|
||||
Dear <t t-out="object.work_order_contacts and object.work_order_contacts[0].name or object.partner_id.name"/>
|
||||
,
|
||||
</div>
|
||||
<div style="margin-bottom: 16px;">
|
||||
We would like to confirm the details of your upcoming service visit:
|
||||
</div>
|
||||
<table style="width:100%;border-spacing:0;border:1px solid #e7e7e7;">
|
||||
<tr>
|
||||
<td style="padding:10px;background-color:#f8f9fa;border-bottom:1px solid #e7e7e7;">Task Summary</td>
|
||||
<td style="padding:10px;border-bottom:1px solid #e7e7e7;">
|
||||
<t t-out="object.name"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:10px;background-color:#f8f9fa;border-bottom:1px solid #e7e7e7;">Technician Arrival</td>
|
||||
<td style="padding:10px;border-bottom:1px solid #e7e7e7;">
|
||||
<t t-set="partner" t-value="object.work_order_contacts and object.work_order_contacts[0] or object.partner_id"/>
|
||||
<t t-set="partner_tz" t-value="user.tz or object.company_id.partner_id.tz or 'America/Toronto'"/>
|
||||
<span t-field="object.planned_date_begin" t-options="{'widget': 'datetime', 'timezone': partner_tz}"/>
|
||||
<div style="font-size: 12px; color: #666;">
|
||||
(Timezone: <t t-out="partner_tz"/>
|
||||
)
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:10px;background-color:#f8f9fa;border-bottom:1px solid #e7e7e7;">Location</td>
|
||||
<td style="padding:10px;border-bottom:1px solid #e7e7e7;">
|
||||
<t t-out="object.partner_id.street"/>
|
||||
<br/>
|
||||
<t t-if="object.partner_id.street2">
|
||||
<t t-out="object.partner_id.street2"/>
|
||||
<br/>
|
||||
</t>
|
||||
<t t-out="object.partner_id.city"/>
|
||||
, <t t-out="object.partner_id.state_id.code"/>
|
||||
<t t-out="object.partner_id.zip"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:10px;background-color:#f8f9fa;">Assigned Technician(s)</td>
|
||||
<td style="padding:10px;">
|
||||
<t t-foreach="object.user_ids" t-as="user">
|
||||
<t t-out="user.name"/>
|
||||
<t t-if="not user_last">
|
||||
<br/>
|
||||
</t>
|
||||
</t>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div style="margin: 16px 0px 16px 0px;">
|
||||
Please take a moment to confirm this visit by clicking one of the following options:
|
||||
</div>
|
||||
<table style="width:100%;border-spacing:0;">
|
||||
<tr>
|
||||
<td style="padding:10px;text-align:center;">
|
||||
<t t-set="base_url" t-value="object.get_base_url()"/>
|
||||
<t t-set="lang" t-value="object.work_order_contacts and object.work_order_contacts[0].lang or object.partner_id.lang or 'en_US'"/>
|
||||
<a t-att-href="base_url + '/my/fsm_confirmation/approve?' + keep_query(lang=lang,access_token=object.access_token)" style="background-color:#28a745;padding:8px 16px;text-decoration:none;color:#fff;border-radius:5px;font-size:13px;">
|
||||
Approve Visit
|
||||
</a>
|
||||
</td>
|
||||
<td style="padding:10px;text-align:center;">
|
||||
<a t-att-href="base_url + '/my/fsm_confirmation/change?' + keep_query(lang=lang,access_token=object.access_token)" style="background-color:#dc3545;padding:8px 16px;text-decoration:none;color:#fff;border-radius:5px;font-size:13px;">
|
||||
Request Changes
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div style="margin-top: 16px;">
|
||||
<p>Best regards,</p>
|
||||
<p>The <t t-out="object.company_id.name"/>
|
||||
service management team</p>
|
||||
</div>
|
||||
</div>
|
||||
</field>
|
||||
<field name="lang">{{ object.work_order_contacts and object.work_order_contacts[0].lang or object.partner_id.lang }}</field>
|
||||
<field name="auto_delete" eval="False"/>
|
||||
</record>
|
||||
</odoo>
|
||||
299
fsm_visit_confirmation/i18n/fr.po
Normal file
299
fsm_visit_confirmation/i18n/fr.po
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * fsm_visit_confirmation
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 17.0+e\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-02-27 00:31+0000\n"
|
||||
"PO-Revision-Date: 2025-02-27 00:31+0000\n"
|
||||
"Last-Translator: Marc Durepos <marc@bemade.org>\n"
|
||||
"Language-Team: French\n"
|
||||
"Language: fr\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"
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#: model:mail.template,body_html:fsm_visit_confirmation.fsm_visit_confirmation_email_template
|
||||
msgid ""
|
||||
"<div>\n"
|
||||
" <div style=\"margin-bottom: 16px;\">\n"
|
||||
" Dear <t t-out=\"object.work_order_contacts and object.work_order_contacts[0].name or object.partner_id.name\"></t>\n"
|
||||
",\n"
|
||||
" </div>\n"
|
||||
" <div style=\"margin-bottom: 16px;\">\n"
|
||||
" We would like to confirm the details of your upcoming service visit:\n"
|
||||
" </div>\n"
|
||||
" <table style=\"border-style:solid;box-sizing:border-box;border-left-color:#e7e7e7;border-bottom-color:#e7e7e7;border-right-color:#e7e7e7;border-top-color:#e7e7e7;border-left-width:1px;border-bottom-width:1px;border-right-width:1px;border-top-width:1px;-webkit-border-vertical-spacing:0px;-webkit-border-horizontal-spacing:0px;border-collapse:collapse;caption-side:bottom;width:100%;border-spacing:0;border:1pxsolid#e7e7e7\" width=\"100%\">\n"
|
||||
" <tbody style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px\"><tr style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px\">\n"
|
||||
" <td style=\"border-style:solid;box-sizing:border-box;border-bottom-color:#e7e7e7;border-left-width:0px;border-bottom-width:1px;border-right-width:0px;border-top-width:0px;padding:10px;background-color:#f8f9fa;border-bottom:1pxsolid#e7e7e7\">Task Summary</td>\n"
|
||||
" <td style=\"border-style:solid;box-sizing:border-box;border-bottom-color:#e7e7e7;border-left-width:0px;border-bottom-width:1px;border-right-width:0px;border-top-width:0px;padding:10px;border-bottom:1pxsolid#e7e7e7\">\n"
|
||||
" <t t-out=\"object.name\"></t>\n"
|
||||
" </td>\n"
|
||||
" </tr>\n"
|
||||
" <tr style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px\">\n"
|
||||
" <td style=\"border-style:solid;box-sizing:border-box;border-bottom-color:#e7e7e7;border-left-width:0px;border-bottom-width:1px;border-right-width:0px;border-top-width:0px;padding:10px;background-color:#f8f9fa;border-bottom:1pxsolid#e7e7e7\">Technician Arrival</td>\n"
|
||||
" <td style=\"border-style:solid;box-sizing:border-box;border-bottom-color:#e7e7e7;border-left-width:0px;border-bottom-width:1px;border-right-width:0px;border-top-width:0px;padding:10px;border-bottom:1pxsolid#e7e7e7\">\n"
|
||||
" <t t-set=\"partner\" t-value=\"object.work_order_contacts and object.work_order_contacts[0] or object.partner_id\"></t>\n"
|
||||
" <t t-set=\"partner_tz\" t-value=\"user.tz or object.company_id.partner_id.tz or 'America/Toronto'\"></t>\n"
|
||||
" <span t-field=\"object.planned_date_begin\" t-options=\"{'widget': 'datetime', 'timezone': partner_tz}\"></span>\n"
|
||||
" <div style=\"font-size: 12px; color: #666;\">\n"
|
||||
" (Timezone: <t t-out=\"partner_tz\"></t>\n"
|
||||
")\n"
|
||||
" </div>\n"
|
||||
" </td>\n"
|
||||
" </tr>\n"
|
||||
" <tr style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px\">\n"
|
||||
" <td style=\"border-style:solid;box-sizing:border-box;border-bottom-color:#e7e7e7;border-left-width:0px;border-bottom-width:1px;border-right-width:0px;border-top-width:0px;padding:10px;background-color:#f8f9fa;border-bottom:1pxsolid#e7e7e7\">Location</td>\n"
|
||||
" <td style=\"border-style:solid;box-sizing:border-box;border-bottom-color:#e7e7e7;border-left-width:0px;border-bottom-width:1px;border-right-width:0px;border-top-width:0px;padding:10px;border-bottom:1pxsolid#e7e7e7\">\n"
|
||||
" <t t-out=\"object.partner_id.street\"></t>\n"
|
||||
" <br>\n"
|
||||
" <t t-if=\"object.partner_id.street2\">\n"
|
||||
" <t t-out=\"object.partner_id.street2\"></t>\n"
|
||||
" <br>\n"
|
||||
" </t>\n"
|
||||
" <t t-out=\"object.partner_id.city\"></t>\n"
|
||||
", <t t-out=\"object.partner_id.state_id.code\"></t>\n"
|
||||
" <t t-out=\"object.partner_id.zip\"></t>\n"
|
||||
" </td>\n"
|
||||
" </tr>\n"
|
||||
" <tr style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px\">\n"
|
||||
" <td style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px;padding:10px;background-color:#f8f9fa\">Assigned Technician(s)</td>\n"
|
||||
" <td style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px;padding:10px\">\n"
|
||||
" <t t-foreach=\"object.user_ids\" t-as=\"user\">\n"
|
||||
" <t t-out=\"user.name\"></t>\n"
|
||||
" <t t-if=\"not user_last\">\n"
|
||||
" <br>\n"
|
||||
" </t>\n"
|
||||
" </t>\n"
|
||||
" </td>\n"
|
||||
" </tr>\n"
|
||||
" </tbody></table>\n"
|
||||
" <div style=\"margin: 16px 0px 16px 0px;\">\n"
|
||||
" Please take a moment to confirm this visit by clicking one of the following options:\n"
|
||||
" </div>\n"
|
||||
" <table style=\"box-sizing:border-box;-webkit-border-vertical-spacing:0px;-webkit-border-horizontal-spacing:0px;border-collapse:collapse;caption-side:bottom;width:100%;border-spacing:0;\" width=\"100%\">\n"
|
||||
" <tbody style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px\"><tr style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px\">\n"
|
||||
" <td style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px;padding:10px;text-align:center\">\n"
|
||||
" <t t-set=\"base_url\" t-value=\"object.get_base_url()\"></t>\n"
|
||||
" <t t-set=\"lang\" t-value=\"object.work_order_contacts and object.work_order_contacts[0].lang or object.partner_id.lang or 'en_US'\"></t>\n"
|
||||
" <a t-att-href=\"base_url + '/my/fsm_confirmation/approve?access_token=' + object.access_token + '&lang=' + lang\" style=\"box-sizing:border-box;background-color:#28a745;padding:8px 16px;text-decoration:none;color:#fff;border-radius:5px;font-size:13px;\">\n"
|
||||
" Approve Visit\n"
|
||||
" </a>\n"
|
||||
" </td>\n"
|
||||
" <td style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px;padding:10px;text-align:center\">\n"
|
||||
" <a t-att-href=\"base_url + '/my/fsm_confirmation/change?access_token=' + object.access_token + '&lang=' + lang\" style=\"box-sizing:border-box;background-color:#dc3545;padding:8px 16px;text-decoration:none;color:#fff;border-radius:5px;font-size:13px;\">\n"
|
||||
" Request Changes\n"
|
||||
" </a>\n"
|
||||
" </td>\n"
|
||||
" </tr>\n"
|
||||
" </tbody></table>\n"
|
||||
" <div style=\"margin-top: 16px;\">\n"
|
||||
" <p style=\"margin:0px 0 16px 0;box-sizing:border-box;\">Best regards,</p>\n"
|
||||
" <p style=\"margin:0px 0 16px 0;box-sizing:border-box;\">The <t t-out=\"object.company_id.name\"></t>\n"
|
||||
" service management team</p>\n"
|
||||
" </div>\n"
|
||||
" </div>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"<div>\n"
|
||||
" <div style=\"margin-bottom: 16px;\">\n"
|
||||
" Cher/chère<t t-out=\"object.work_order_contacts and object.work_order_contacts[0].name or object.partner_id.name\"></t>\n"
|
||||
",\n"
|
||||
" </div>\n"
|
||||
" <div style=\"margin-bottom: 16px;\">\n"
|
||||
" Nous aimerions confirmer les détails de votre visite de service à venir.\n"
|
||||
" </div>\n"
|
||||
" <table style=\"border-style:solid;box-sizing:border-box;border-left-color:#e7e7e7;border-bottom-color:#e7e7e7;border-right-color:#e7e7e7;border-top-color:#e7e7e7;border-left-width:1px;border-bottom-width:1px;border-right-width:1px;border-top-width:1px;-webkit-border-vertical-spacing:0px;-webkit-border-horizontal-spacing:0px;border-collapse:collapse;caption-side:bottom;width:100%;border-spacing:0;border:1pxsolid#e7e7e7\" width=\"100%\">\n"
|
||||
" <tbody style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px\"><tr style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px\">\n"
|
||||
" <td style=\"border-style:solid;box-sizing:border-box;border-bottom-color:#e7e7e7;border-left-width:0px;border-bottom-width:1px;border-right-width:0px;border-top-width:0px;padding:10px;background-color:#f8f9fa;border-bottom:1pxsolid#e7e7e7\">Visite</td>\n"
|
||||
" <td style=\"border-style:solid;box-sizing:border-box;border-bottom-color:#e7e7e7;border-left-width:0px;border-bottom-width:1px;border-right-width:0px;border-top-width:0px;padding:10px;border-bottom:1pxsolid#e7e7e7\">\n"
|
||||
" <t t-out=\"object.name\"></t>\n"
|
||||
" </td>\n"
|
||||
" </tr>\n"
|
||||
" <tr style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px\">\n"
|
||||
" <td style=\"border-style:solid;box-sizing:border-box;border-bottom-color:#e7e7e7;border-left-width:0px;border-bottom-width:1px;border-right-width:0px;border-top-width:0px;padding:10px;background-color:#f8f9fa;border-bottom:1pxsolid#e7e7e7\">Début</td>\n"
|
||||
" <td style=\"border-style:solid;box-sizing:border-box;border-bottom-color:#e7e7e7;border-left-width:0px;border-bottom-width:1px;border-right-width:0px;border-top-width:0px;padding:10px;border-bottom:1pxsolid#e7e7e7\">\n"
|
||||
" <t t-set=\"partner\" t-value=\"object.work_order_contacts and object.work_order_contacts[0] or object.partner_id\"></t>\n"
|
||||
" <t t-set=\"partner_tz\" t-value=\"user.tz or object.company_id.partner_id.tz or 'America/Toronto'\"></t>\n"
|
||||
" <span t-field=\"object.planned_date_begin\" t-options=\"{'widget': 'datetime', 'timezone': partner_tz}\"></span>\n"
|
||||
" <div style=\"font-size: 12px; color: #666;\">\n"
|
||||
" (Fuseau horaire: <t t-out=\"partner_tz\"></t>\n"
|
||||
")\n"
|
||||
" </div>\n"
|
||||
" </td>\n"
|
||||
" </tr>\n"
|
||||
" <tr style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px\">\n"
|
||||
" <td style=\"border-style:solid;box-sizing:border-box;border-bottom-color:#e7e7e7;border-left-width:0px;border-bottom-width:1px;border-right-width:0px;border-top-width:0px;padding:10px;background-color:#f8f9fa;border-bottom:1pxsolid#e7e7e7\">Lieu</td>\n"
|
||||
" <td style=\"border-style:solid;box-sizing:border-box;border-bottom-color:#e7e7e7;border-left-width:0px;border-bottom-width:1px;border-right-width:0px;border-top-width:0px;padding:10px;border-bottom:1pxsolid#e7e7e7\">\n"
|
||||
" <t t-out=\"object.partner_id.street\"></t>\n"
|
||||
" <br>\n"
|
||||
" <t t-if=\"object.partner_id.street2\">\n"
|
||||
" <t t-out=\"object.partner_id.street2\"></t>\n"
|
||||
" <br>\n"
|
||||
" </t>\n"
|
||||
" <t t-out=\"object.partner_id.city\"></t>\n"
|
||||
", <t t-out=\"object.partner_id.state_id.code\"></t>\n"
|
||||
" <t t-out=\"object.partner_id.zip\"></t>\n"
|
||||
" </td>\n"
|
||||
" </tr>\n"
|
||||
" <tr style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px\">\n"
|
||||
" <td style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px;padding:10px;background-color:#f8f9fa\">Technicien(s) assigné(s)</td>\n"
|
||||
" <td style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px;padding:10px\">\n"
|
||||
" <t t-foreach=\"object.user_ids\" t-as=\"user\">\n"
|
||||
" <t t-out=\"user.name\"></t>\n"
|
||||
" <t t-if=\"not user_last\">\n"
|
||||
" <br>\n"
|
||||
" </t>\n"
|
||||
" </t>\n"
|
||||
" </td>\n"
|
||||
" </tr>\n"
|
||||
" </tbody></table>\n"
|
||||
" <div style=\"margin: 16px 0px 16px 0px;\">\n"
|
||||
" Veuillez prendre un moment pour confirmer cette visite en cliquant sur une des options suivantes :\n"
|
||||
" </div>\n"
|
||||
" <table style=\"box-sizing:border-box;-webkit-border-vertical-spacing:0px;-webkit-border-horizontal-spacing:0px;border-collapse:collapse;caption-side:bottom;width:100%;border-spacing:0;\" width=\"100%\">\n"
|
||||
" <tbody style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px\"><tr style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px\">\n"
|
||||
" <td style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px;padding:10px;text-align:center\">\n"
|
||||
" <t t-set=\"base_url\" t-value=\"object.get_base_url()\"></t>\n"
|
||||
" <t t-set=\"lang\" t-value=\"object.work_order_contacts and object.work_order_contacts[0].lang or object.partner_id.lang or 'en_US'\"></t>\n"
|
||||
" <a t-att-href=\"base_url + '/my/fsm_confirmation/approve?access_token=' + object.access_token + '&lang=' + lang\" style=\"box-sizing:border-box;background-color:#28a745;padding:8px 16px;text-decoration:none;color:#fff;border-radius:5px;font-size:13px;\">\n"
|
||||
" Confirmer la visite\n"
|
||||
" </a>\n"
|
||||
" </td>\n"
|
||||
" <td style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px;padding:10px;text-align:center\">\n"
|
||||
" <a t-att-href=\"base_url + '/my/fsm_confirmation/change?access_token=' + object.access_token + '&lang=' + lang\" style=\"box-sizing:border-box;background-color:#dc3545;padding:8px 16px;text-decoration:none;color:#fff;border-radius:5px;font-size:13px;\">\n"
|
||||
" Demander des changements\n"
|
||||
" </a>\n"
|
||||
" </td>\n"
|
||||
" </tr>\n"
|
||||
" </tbody></table>\n"
|
||||
" <div style=\"margin-top: 16px;\">\n"
|
||||
" <p style=\"margin:0px 0 16px 0;box-sizing:border-box;\">Cordialement,</p>\n"
|
||||
" <p style=\"margin:0px 0 16px 0;box-sizing:border-box;\">L'équipe de gestion des services de <t t-out=\"object.company_id.name\"></t></p>\n"
|
||||
" </div>\n"
|
||||
" </div>\n"
|
||||
" "
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#: model_terms:ir.ui.view,arch_db:fsm_visit_confirmation.portal_my_task
|
||||
msgid ""
|
||||
"<i class=\"fa fa-check-circle me-2\"/>\n"
|
||||
" <strong>Success!</strong> Thank you for approving this service visit."
|
||||
msgstr ""
|
||||
"<i class=\"fa fa-check-circle me-2\"/>\n"
|
||||
" <strong>Succès!</strong> Merci d'avoir approuvé cette visite de service."
|
||||
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#: model_terms:ir.ui.view,arch_db:fsm_visit_confirmation.portal_my_task
|
||||
msgid ""
|
||||
"<i class=\"fa fa-exclamation-triangle me-2\"/>\n"
|
||||
" Request Changes to Service Visit"
|
||||
msgstr ""
|
||||
"<i class=\"fa fa-exclamation-triangle me-2\"/>\n"
|
||||
" Demander des changements à la visite de service"
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#: model_terms:ir.ui.view,arch_db:fsm_visit_confirmation.portal_my_task
|
||||
msgid ""
|
||||
"<i class=\"fa fa-info-circle me-2\"/>\n"
|
||||
" <strong>Thank you!</strong> Your change request has been submitted. Our team will review it shortly."
|
||||
msgstr ""
|
||||
"<i class=\"fa fa-info-circle me-2\"/>\n"
|
||||
" <strong>Merci!</strong> Votre demande de changement a été soumise. Notre équipe examinerait cette demande rapidement."
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#. odoo-python
|
||||
#: code:addons/fsm_visit_confirmation/controllers/main.py:0
|
||||
#, python-format
|
||||
msgid "Changes requested by customer: %s"
|
||||
msgstr "Changements demandés par client: %s"
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#. odoo-python
|
||||
#: code:addons/fsm_visit_confirmation/controllers/main.py:0
|
||||
#: code:addons/fsm_visit_confirmation/controllers/main.py:0
|
||||
#, python-format
|
||||
msgid "Error approving task: %s"
|
||||
msgstr "Erreur lors de l'approbation de la tâche: %s"
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#. odoo-python
|
||||
#: code:addons/fsm_visit_confirmation/controllers/main.py:0
|
||||
#: code:addons/fsm_visit_confirmation/controllers/main.py:0
|
||||
#, python-format
|
||||
msgid "Error submitting change request: %s"
|
||||
msgstr "Erreur lors de la soumission de la demande de changement: %s"
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#: model:mail.template,name:fsm_visit_confirmation.fsm_visit_confirmation_email_template
|
||||
msgid "FSM Visit Confirmation"
|
||||
msgstr "Confirmation de visite de service"
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#. odoo-python
|
||||
#: code:addons/fsm_visit_confirmation/controllers/main.py:0
|
||||
#, python-format
|
||||
msgid "Invalid action"
|
||||
msgstr "Action invalide"
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#. odoo-python
|
||||
#: code:addons/fsm_visit_confirmation/controllers/main.py:0
|
||||
#, python-format
|
||||
msgid "No feedback provided. Please try again!"
|
||||
msgstr "Aucun commentaire fourni. Veuillez réessayer!"
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#: model_terms:ir.ui.view,arch_db:fsm_visit_confirmation.portal_my_task
|
||||
msgid "Please explain what changes you need..."
|
||||
msgstr "Veuillez expliquer les modifications nécessaires..."
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#: model_terms:ir.ui.view,arch_db:fsm_visit_confirmation.portal_my_task
|
||||
msgid ""
|
||||
"Please provide details about the changes you would like to make to this "
|
||||
"service visit:"
|
||||
msgstr ""
|
||||
"Veuillez fournir des informations sur les modifications que vous souhaitez "
|
||||
"apporter à cette visite de service :"
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#: model:mail.template,description:fsm_visit_confirmation.fsm_visit_confirmation_email_template
|
||||
msgid "Send a visit confirmation email to the customer"
|
||||
msgstr "Envoyer un email de confirmation de visite au client"
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#: model:mail.template,subject:fsm_visit_confirmation.fsm_visit_confirmation_email_template
|
||||
msgid "Service Visit Confirmation: {{ object.name }}"
|
||||
msgstr "Confirmation de visite de service: {{ object.name }}"
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#: model_terms:ir.ui.view,arch_db:fsm_visit_confirmation.portal_my_task
|
||||
msgid "Submit Request"
|
||||
msgstr "Soumettre la demande"
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#. odoo-python
|
||||
#: code:addons/fsm_visit_confirmation/controllers/main.py:0
|
||||
#: code:addons/fsm_visit_confirmation/controllers/main.py:0
|
||||
#: code:addons/fsm_visit_confirmation/controllers/main.py:0
|
||||
#, python-format
|
||||
msgid "Task not found or invalid token"
|
||||
msgstr "Tâche introuvable ou jeton invalide"
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#. odoo-python
|
||||
#: code:addons/fsm_visit_confirmation/controllers/main.py:0
|
||||
#, python-format
|
||||
msgid "Visit approved by customer"
|
||||
msgstr "Visite approuvée par le client"
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#: model_terms:ir.ui.view,arch_db:fsm_visit_confirmation.portal_my_task
|
||||
msgid "Your Comments:"
|
||||
msgstr "Vos commentaires :"
|
||||
211
fsm_visit_confirmation/i18n/fsm_visit_confirmation.pot
Normal file
211
fsm_visit_confirmation/i18n/fsm_visit_confirmation.pot
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * fsm_visit_confirmation
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 17.0+e\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-02-27 00:31+0000\n"
|
||||
"PO-Revision-Date: 2025-02-27 00:31+0000\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: fsm_visit_confirmation
|
||||
#: model:mail.template,body_html:fsm_visit_confirmation.fsm_visit_confirmation_email_template
|
||||
msgid ""
|
||||
"<div>\n"
|
||||
" <div style=\"margin-bottom: 16px;\">\n"
|
||||
" Dear <t t-out=\"object.work_order_contacts and object.work_order_contacts[0].name or object.partner_id.name\"></t>\n"
|
||||
",\n"
|
||||
" </div>\n"
|
||||
" <div style=\"margin-bottom: 16px;\">\n"
|
||||
" We would like to confirm the details of your upcoming service visit:\n"
|
||||
" </div>\n"
|
||||
" <table style=\"border-style:solid;box-sizing:border-box;border-left-color:#e7e7e7;border-bottom-color:#e7e7e7;border-right-color:#e7e7e7;border-top-color:#e7e7e7;border-left-width:1px;border-bottom-width:1px;border-right-width:1px;border-top-width:1px;-webkit-border-vertical-spacing:0px;-webkit-border-horizontal-spacing:0px;border-collapse:collapse;caption-side:bottom;width:100%;border-spacing:0;border:1pxsolid#e7e7e7\" width=\"100%\">\n"
|
||||
" <tbody style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px\"><tr style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px\">\n"
|
||||
" <td style=\"border-style:solid;box-sizing:border-box;border-bottom-color:#e7e7e7;border-left-width:0px;border-bottom-width:1px;border-right-width:0px;border-top-width:0px;padding:10px;background-color:#f8f9fa;border-bottom:1pxsolid#e7e7e7\">Task Summary</td>\n"
|
||||
" <td style=\"border-style:solid;box-sizing:border-box;border-bottom-color:#e7e7e7;border-left-width:0px;border-bottom-width:1px;border-right-width:0px;border-top-width:0px;padding:10px;border-bottom:1pxsolid#e7e7e7\">\n"
|
||||
" <t t-out=\"object.name\"></t>\n"
|
||||
" </td>\n"
|
||||
" </tr>\n"
|
||||
" <tr style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px\">\n"
|
||||
" <td style=\"border-style:solid;box-sizing:border-box;border-bottom-color:#e7e7e7;border-left-width:0px;border-bottom-width:1px;border-right-width:0px;border-top-width:0px;padding:10px;background-color:#f8f9fa;border-bottom:1pxsolid#e7e7e7\">Technician Arrival</td>\n"
|
||||
" <td style=\"border-style:solid;box-sizing:border-box;border-bottom-color:#e7e7e7;border-left-width:0px;border-bottom-width:1px;border-right-width:0px;border-top-width:0px;padding:10px;border-bottom:1pxsolid#e7e7e7\">\n"
|
||||
" <t t-set=\"partner\" t-value=\"object.work_order_contacts and object.work_order_contacts[0] or object.partner_id\"></t>\n"
|
||||
" <t t-set=\"partner_tz\" t-value=\"user.tz or object.company_id.partner_id.tz or 'America/Toronto'\"></t>\n"
|
||||
" <span t-field=\"object.planned_date_begin\" t-options=\"{'widget': 'datetime', 'timezone': partner_tz}\"></span>\n"
|
||||
" <div style=\"font-size: 12px; color: #666;\">\n"
|
||||
" (Timezone: <t t-out=\"partner_tz\"></t>\n"
|
||||
")\n"
|
||||
" </div>\n"
|
||||
" </td>\n"
|
||||
" </tr>\n"
|
||||
" <tr style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px\">\n"
|
||||
" <td style=\"border-style:solid;box-sizing:border-box;border-bottom-color:#e7e7e7;border-left-width:0px;border-bottom-width:1px;border-right-width:0px;border-top-width:0px;padding:10px;background-color:#f8f9fa;border-bottom:1pxsolid#e7e7e7\">Location</td>\n"
|
||||
" <td style=\"border-style:solid;box-sizing:border-box;border-bottom-color:#e7e7e7;border-left-width:0px;border-bottom-width:1px;border-right-width:0px;border-top-width:0px;padding:10px;border-bottom:1pxsolid#e7e7e7\">\n"
|
||||
" <t t-out=\"object.partner_id.street\"></t>\n"
|
||||
" <br>\n"
|
||||
" <t t-if=\"object.partner_id.street2\">\n"
|
||||
" <t t-out=\"object.partner_id.street2\"></t>\n"
|
||||
" <br>\n"
|
||||
" </t>\n"
|
||||
" <t t-out=\"object.partner_id.city\"></t>\n"
|
||||
", <t t-out=\"object.partner_id.state_id.code\"></t>\n"
|
||||
" <t t-out=\"object.partner_id.zip\"></t>\n"
|
||||
" </td>\n"
|
||||
" </tr>\n"
|
||||
" <tr style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px\">\n"
|
||||
" <td style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px;padding:10px;background-color:#f8f9fa\">Assigned Technician(s)</td>\n"
|
||||
" <td style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px;padding:10px\">\n"
|
||||
" <t t-foreach=\"object.user_ids\" t-as=\"user\">\n"
|
||||
" <t t-out=\"user.name\"></t>\n"
|
||||
" <t t-if=\"not user_last\">\n"
|
||||
" <br>\n"
|
||||
" </t>\n"
|
||||
" </t>\n"
|
||||
" </td>\n"
|
||||
" </tr>\n"
|
||||
" </tbody></table>\n"
|
||||
" <div style=\"margin: 16px 0px 16px 0px;\">\n"
|
||||
" Please take a moment to confirm this visit by clicking one of the following options:\n"
|
||||
" </div>\n"
|
||||
" <table style=\"box-sizing:border-box;-webkit-border-vertical-spacing:0px;-webkit-border-horizontal-spacing:0px;border-collapse:collapse;caption-side:bottom;width:100%;border-spacing:0;\" width=\"100%\">\n"
|
||||
" <tbody style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px\"><tr style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px\">\n"
|
||||
" <td style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px;padding:10px;text-align:center\">\n"
|
||||
" <t t-set=\"base_url\" t-value=\"object.get_base_url()\"></t>\n"
|
||||
" <t t-set=\"lang\" t-value=\"object.work_order_contacts and object.work_order_contacts[0].lang or object.partner_id.lang or 'en_US'\"></t>\n"
|
||||
" <a t-att-href=\"base_url + '/my/fsm_confirmation/approve?access_token=' + object.access_token + '&lang=' + lang\" style=\"box-sizing:border-box;background-color:#28a745;padding:8px 16px;text-decoration:none;color:#fff;border-radius:5px;font-size:13px;\">\n"
|
||||
" Approve Visit\n"
|
||||
" </a>\n"
|
||||
" </td>\n"
|
||||
" <td style=\"border-style:none;box-sizing:border-box;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;border-top-width:0px;padding:10px;text-align:center\">\n"
|
||||
" <a t-att-href=\"base_url + '/my/fsm_confirmation/change?access_token=' + object.access_token + '&lang=' + lang\" style=\"box-sizing:border-box;background-color:#dc3545;padding:8px 16px;text-decoration:none;color:#fff;border-radius:5px;font-size:13px;\">\n"
|
||||
" Request Changes\n"
|
||||
" </a>\n"
|
||||
" </td>\n"
|
||||
" </tr>\n"
|
||||
" </tbody></table>\n"
|
||||
" <div style=\"margin-top: 16px;\">\n"
|
||||
" <p style=\"margin:0px 0 16px 0;box-sizing:border-box;\">Best regards,</p>\n"
|
||||
" <p style=\"margin:0px 0 16px 0;box-sizing:border-box;\">The <t t-out=\"object.company_id.name\"></t>\n"
|
||||
" service management team</p>\n"
|
||||
" </div>\n"
|
||||
" </div>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#: model_terms:ir.ui.view,arch_db:fsm_visit_confirmation.portal_my_task
|
||||
msgid ""
|
||||
"<i class=\"fa fa-check-circle me-2\"/>\n"
|
||||
" <strong>Success!</strong> Thank you for approving this service visit."
|
||||
msgstr ""
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#: model_terms:ir.ui.view,arch_db:fsm_visit_confirmation.portal_my_task
|
||||
msgid ""
|
||||
"<i class=\"fa fa-exclamation-triangle me-2\"/>\n"
|
||||
" Request Changes to Service Visit"
|
||||
msgstr ""
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#: model_terms:ir.ui.view,arch_db:fsm_visit_confirmation.portal_my_task
|
||||
msgid ""
|
||||
"<i class=\"fa fa-info-circle me-2\"/>\n"
|
||||
" <strong>Thank you!</strong> Your change request has been submitted. Our team will review it shortly."
|
||||
msgstr ""
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#. odoo-python
|
||||
#: code:addons/fsm_visit_confirmation/controllers/main.py:0
|
||||
#, python-format
|
||||
msgid "Changes requested by customer: %s"
|
||||
msgstr ""
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#. odoo-python
|
||||
#: code:addons/fsm_visit_confirmation/controllers/main.py:0
|
||||
#: code:addons/fsm_visit_confirmation/controllers/main.py:0
|
||||
#, python-format
|
||||
msgid "Error approving task: %s"
|
||||
msgstr ""
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#. odoo-python
|
||||
#: code:addons/fsm_visit_confirmation/controllers/main.py:0
|
||||
#: code:addons/fsm_visit_confirmation/controllers/main.py:0
|
||||
#, python-format
|
||||
msgid "Error submitting change request: %s"
|
||||
msgstr ""
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#: model:mail.template,name:fsm_visit_confirmation.fsm_visit_confirmation_email_template
|
||||
msgid "FSM Visit Confirmation"
|
||||
msgstr ""
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#. odoo-python
|
||||
#: code:addons/fsm_visit_confirmation/controllers/main.py:0
|
||||
#, python-format
|
||||
msgid "Invalid action"
|
||||
msgstr ""
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#. odoo-python
|
||||
#: code:addons/fsm_visit_confirmation/controllers/main.py:0
|
||||
#, python-format
|
||||
msgid "No feedback provided. Please try again!"
|
||||
msgstr ""
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#: model_terms:ir.ui.view,arch_db:fsm_visit_confirmation.portal_my_task
|
||||
msgid "Please explain what changes you need..."
|
||||
msgstr ""
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#: model_terms:ir.ui.view,arch_db:fsm_visit_confirmation.portal_my_task
|
||||
msgid ""
|
||||
"Please provide details about the changes you would like to make to this "
|
||||
"service visit:"
|
||||
msgstr ""
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#: model:mail.template,description:fsm_visit_confirmation.fsm_visit_confirmation_email_template
|
||||
msgid "Send a visit confirmation email to the customer"
|
||||
msgstr ""
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#: model:mail.template,subject:fsm_visit_confirmation.fsm_visit_confirmation_email_template
|
||||
msgid "Service Visit Confirmation: {{ object.name }}"
|
||||
msgstr ""
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#: model_terms:ir.ui.view,arch_db:fsm_visit_confirmation.portal_my_task
|
||||
msgid "Submit Request"
|
||||
msgstr ""
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#. odoo-python
|
||||
#: code:addons/fsm_visit_confirmation/controllers/main.py:0
|
||||
#: code:addons/fsm_visit_confirmation/controllers/main.py:0
|
||||
#: code:addons/fsm_visit_confirmation/controllers/main.py:0
|
||||
#, python-format
|
||||
msgid "Task not found or invalid token"
|
||||
msgstr ""
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#. odoo-python
|
||||
#: code:addons/fsm_visit_confirmation/controllers/main.py:0
|
||||
#, python-format
|
||||
msgid "Visit approved by customer"
|
||||
msgstr ""
|
||||
|
||||
#. module: fsm_visit_confirmation
|
||||
#: model_terms:ir.ui.view,arch_db:fsm_visit_confirmation.portal_my_task
|
||||
msgid "Your Comments:"
|
||||
msgstr ""
|
||||
2
fsm_visit_confirmation/models/__init__.py
Normal file
2
fsm_visit_confirmation/models/__init__.py
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
from . import project_task_type
|
||||
from . import project_task
|
||||
31
fsm_visit_confirmation/models/project_task.py
Normal file
31
fsm_visit_confirmation/models/project_task.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
from odoo import models
|
||||
|
||||
|
||||
class ProjectTask(models.Model):
|
||||
_inherit = "project.task"
|
||||
|
||||
def write(self, vals):
|
||||
# Store old stage for comparison
|
||||
old_stages = {task.id: task.stage_id for task in self}
|
||||
|
||||
# Execute original write method
|
||||
result = super().write(vals)
|
||||
|
||||
# If stage changed and new stage has an approval template, send the email
|
||||
if "stage_id" in vals:
|
||||
# Only send email for top-level tasks
|
||||
for task in self.filtered(
|
||||
lambda task: task.stage_id != old_stages[task.id]
|
||||
and task.stage_id.approval_template_id
|
||||
and not task.parent_id
|
||||
):
|
||||
task._portal_ensure_token()
|
||||
task.stage_id.approval_template_id.send_mail(
|
||||
task.id,
|
||||
force_send=True,
|
||||
email_values={
|
||||
"email_to": task.partner_id.email if task.partner_id else False
|
||||
},
|
||||
)
|
||||
|
||||
return result
|
||||
11
fsm_visit_confirmation/models/project_task_type.py
Normal file
11
fsm_visit_confirmation/models/project_task_type.py
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
from odoo import fields, models
|
||||
|
||||
|
||||
class ProjectTaskType(models.Model):
|
||||
_inherit = 'project.task.type'
|
||||
|
||||
approval_template_id = fields.Many2one(
|
||||
'mail.template',
|
||||
string='Approval Template',
|
||||
help='Template to use for approval emails when a task reaches this stage.'
|
||||
)
|
||||
1
fsm_visit_confirmation/tests/__init__.py
Normal file
1
fsm_visit_confirmation/tests/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from . import test_fsm_visit_confirmation
|
||||
249
fsm_visit_confirmation/tests/test_fsm_visit_confirmation.py
Normal file
249
fsm_visit_confirmation/tests/test_fsm_visit_confirmation.py
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from odoo import http
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import HttpCase
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@tagged("post_install", "-at_install")
|
||||
class TestFSMVisitConfirmation(HttpCase):
|
||||
|
||||
@classmethod
|
||||
def http_port(cls):
|
||||
"""Override http_port to handle Odoo 18.0 PreforkServer changes"""
|
||||
import odoo.tools.config
|
||||
import odoo.service.server
|
||||
|
||||
try:
|
||||
if odoo.service.server.server is not None:
|
||||
server = odoo.service.server.server
|
||||
# Try to get port from httpd first (older versions)
|
||||
if hasattr(server, 'httpd'):
|
||||
httpd = getattr(server, 'httpd')
|
||||
if hasattr(httpd, 'server_port'):
|
||||
return getattr(httpd, 'server_port')
|
||||
# Fallback to server.port (newer versions like PreforkServer)
|
||||
if hasattr(server, 'port'):
|
||||
return getattr(server, 'port')
|
||||
except (AttributeError, TypeError):
|
||||
pass
|
||||
|
||||
# Final fallback to config
|
||||
return odoo.tools.config['http_port']
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
# Configure test company
|
||||
self.env.company.write(
|
||||
{
|
||||
"email": "company@test.example.com",
|
||||
"name": "Test Company",
|
||||
}
|
||||
)
|
||||
|
||||
# Create test users and partners
|
||||
self.fsm_user = self.env["res.users"].create(
|
||||
{
|
||||
"name": "FSM User",
|
||||
"login": "fsm_user",
|
||||
"email": "fsm@example.com",
|
||||
"groups_id": [
|
||||
(
|
||||
6,
|
||||
0,
|
||||
[
|
||||
self.env.ref("industry_fsm.group_fsm_user").id,
|
||||
],
|
||||
)
|
||||
],
|
||||
}
|
||||
)
|
||||
# Set email on user's partner
|
||||
self.fsm_user.partner_id.email = "fsm@example.com"
|
||||
self.fsm_user.partner_id.lang = "en_US" # Set language explicitly
|
||||
|
||||
self.customer = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "Customer",
|
||||
"email": "customer@example.com",
|
||||
"lang": "en_US", # Set language explicitly
|
||||
}
|
||||
)
|
||||
|
||||
# Create a project
|
||||
self.project = self.env["project.project"].create(
|
||||
{
|
||||
"name": "Test FSM Project",
|
||||
"is_fsm": True,
|
||||
"company_id": self.env.user.company_id.id,
|
||||
}
|
||||
)
|
||||
|
||||
# Create stages for the project
|
||||
self.stage_new = self.env["project.task.type"].create(
|
||||
{
|
||||
"name": "New",
|
||||
"sequence": 0,
|
||||
"project_ids": [(4, self.project.id)],
|
||||
}
|
||||
)
|
||||
|
||||
self.stage_needs_confirmation = self.env["project.task.type"].create(
|
||||
{
|
||||
"name": "Need Confirmation",
|
||||
"sequence": 1,
|
||||
"project_ids": [(4, self.project.id)],
|
||||
}
|
||||
)
|
||||
|
||||
self.stage_approved = self.env["project.task.type"].create(
|
||||
{
|
||||
"name": "Approved",
|
||||
"sequence": 2,
|
||||
"project_ids": [(4, self.project.id)],
|
||||
}
|
||||
)
|
||||
|
||||
self.stage_changes_requested = self.env["project.task.type"].create(
|
||||
{
|
||||
"name": "Changes Requested",
|
||||
"sequence": 3,
|
||||
"project_ids": [(4, self.project.id)],
|
||||
}
|
||||
)
|
||||
|
||||
# Create a task
|
||||
self.task = self.env["project.task"].create(
|
||||
{
|
||||
"name": "Test Task",
|
||||
"project_id": self.project.id,
|
||||
"partner_id": self.customer.id,
|
||||
"work_order_contacts": [(4, self.customer.id)],
|
||||
"user_ids": [(4, self.fsm_user.id)],
|
||||
"planned_date_begin": datetime.now() + timedelta(days=1),
|
||||
"stage_id": self.stage_new.id,
|
||||
"state": "01_in_progress", # Using valid state value
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
self.task.stage_id, self.stage_new, "Task should start in New stage"
|
||||
)
|
||||
|
||||
# Ensure the task has an access token
|
||||
if not self.task.access_token:
|
||||
self.task._portal_ensure_token()
|
||||
self.token = self.task.access_token
|
||||
self.assertTrue(self.token, "Task should have an access token")
|
||||
|
||||
def test_01_task_approve_flow(self):
|
||||
"""Test the complete task approval flow"""
|
||||
from odoo.addons.fsm_visit_confirmation.controllers.main import CustomerPortalExtended
|
||||
from odoo.addons.website.tools import MockRequest
|
||||
from unittest.mock import patch
|
||||
|
||||
# Create controller instance
|
||||
controller = CustomerPortalExtended()
|
||||
|
||||
# Patch HttpCase.http_port to use our safe implementation
|
||||
with patch('odoo.tests.common.HttpCase.http_port', self.http_port):
|
||||
with MockRequest(self.env):
|
||||
# Test the approval action through the controller
|
||||
response = controller.fsm_confirmation_action(
|
||||
"approve", access_token=self.token
|
||||
)
|
||||
|
||||
# Verify we got a redirect response (werkzeug Response object)
|
||||
self.assertTrue(hasattr(response, 'status_code'), "Should return a response object")
|
||||
|
||||
# Check that the task state was updated by the controller
|
||||
self.task.invalidate_recordset()
|
||||
self.assertEqual(
|
||||
self.task.state, "03_approved", "Task state should be updated to approved"
|
||||
)
|
||||
|
||||
# Check that a message was posted by the controller
|
||||
messages = self.env["mail.message"].search(
|
||||
[
|
||||
("model", "=", "project.task"),
|
||||
("res_id", "=", self.task.id),
|
||||
("body", "ilike", "Visit approved by customer"),
|
||||
]
|
||||
)
|
||||
self.assertTrue(messages, "A message should be posted on the task")
|
||||
|
||||
def test_03_stage_approved_sends_email(self):
|
||||
"""Test that moving a task to the approved stage sends an email"""
|
||||
# Set the approval template on the approved stage
|
||||
template = self.env.ref(
|
||||
"fsm_visit_confirmation.fsm_visit_confirmation_email_template"
|
||||
)
|
||||
self.stage_approved.approval_template_id = template
|
||||
|
||||
# Count existing mail messages before the action
|
||||
initial_mail_count = self.env["mail.mail"].search_count([])
|
||||
|
||||
self.task.write({"stage_id": self.stage_approved.id})
|
||||
|
||||
# Check that a new mail was created
|
||||
final_mail_count = self.env["mail.mail"].search_count([])
|
||||
self.assertEqual(
|
||||
final_mail_count - initial_mail_count,
|
||||
1,
|
||||
"Moving task to approved stage should send an email",
|
||||
)
|
||||
|
||||
# Find the new mail and verify recipient
|
||||
new_mail = self.env["mail.mail"].search([], order="id desc", limit=1)
|
||||
self.assertEqual(new_mail.email_to, self.customer.email)
|
||||
|
||||
def test_02_task_request_changes_flow(self):
|
||||
"""Test the complete task request changes flow"""
|
||||
from odoo.addons.fsm_visit_confirmation.controllers.main import CustomerPortalExtended
|
||||
from odoo.addons.website.tools import MockRequest
|
||||
from unittest.mock import patch
|
||||
|
||||
# Create controller instance
|
||||
controller = CustomerPortalExtended()
|
||||
feedback = "Need some changes"
|
||||
|
||||
# Patch HttpCase.http_port to use our safe implementation
|
||||
with patch('odoo.tests.common.HttpCase.http_port', self.http_port):
|
||||
with MockRequest(self.env):
|
||||
# First test the change action (should redirect to form)
|
||||
response = controller.fsm_confirmation_action("change", access_token=self.token)
|
||||
|
||||
# Verify we got a redirect response (werkzeug Response object)
|
||||
self.assertTrue(hasattr(response, 'status_code'), "Should return a response object")
|
||||
|
||||
# Now test the submit change action
|
||||
response = controller.fsm_confirmation_submit_change(
|
||||
access_token=self.token, feedback=feedback
|
||||
)
|
||||
|
||||
# Verify we got a redirect response (werkzeug Response object)
|
||||
self.assertTrue(hasattr(response, 'status_code'), "Should return a response object")
|
||||
|
||||
# Check that the task state was updated by the controller
|
||||
self.task.invalidate_recordset()
|
||||
self.assertEqual(
|
||||
self.task.state,
|
||||
"02_changes_requested",
|
||||
"Task state should be updated to changes requested",
|
||||
)
|
||||
|
||||
# Check that a message was posted with the feedback by the controller
|
||||
messages = self.env["mail.message"].search(
|
||||
[
|
||||
("model", "=", "project.task"),
|
||||
("res_id", "=", self.task.id),
|
||||
("body", "ilike", "Need some changes"),
|
||||
]
|
||||
)
|
||||
self.assertTrue(
|
||||
messages, "A message with feedback should be posted on the task"
|
||||
)
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<template id="portal_my_task" inherit_id="project.portal_my_task">
|
||||
<!-- Add success message when visit is approved -->
|
||||
<xpath expr="//div[@id='task_content']" position="inside">
|
||||
<div t-if="request.params.get('visit_confirmation_status') == 'approved'" class="alert alert-success" role="alert">
|
||||
<i class="fa fa-check-circle me-2"></i>
|
||||
<strong>Success!</strong> Thank you for approving this service visit.
|
||||
</div>
|
||||
<div t-if="request.params.get('visit_confirmation_status') == 'change_submitted'" class="alert alert-info" role="alert">
|
||||
<i class="fa fa-info-circle me-2"></i>
|
||||
<strong>Thank you!</strong> Your change request has been submitted. Our team will review it shortly.
|
||||
</div>
|
||||
</xpath>
|
||||
|
||||
<!-- Add change request form when needed -->
|
||||
<xpath expr="//div[@id='task_chat']" position="before">
|
||||
<div t-if="request.params.get('visit_confirmation_status') == 'change'" class="mb-4 mt-4">
|
||||
<div class="card">
|
||||
<div class="card-header bg-warning text-white">
|
||||
<h5 class="mb-0">
|
||||
<i class="fa fa-exclamation-triangle me-2"></i>
|
||||
Request Changes to Service Visit
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>Please provide details about the changes you would like to make to this service visit:</p>
|
||||
<form action="/my/fsm_confirmation/submit_change" method="post">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="access_token" t-att-value="request.params.get('access_token')"/>
|
||||
<input type="hidden" name="lang" t-att-value="request.context.get('lang') or request.params.get('lang')"/>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label for="feedback" class="form-label">Your Comments:</label>
|
||||
<textarea class="form-control" name="feedback" id="feedback" rows="4" required="required" placeholder="Please explain what changes you need..."></textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-warning">
|
||||
Submit Request
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
13
fsm_visit_confirmation/views/project_task_type_views.xml
Normal file
13
fsm_visit_confirmation/views/project_task_type_views.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_project_task_type_form_inherit_fsm_visit_confirmation" model="ir.ui.view">
|
||||
<field name="name">project.task.type.form.inherit.fsm.visit.confirmation</field>
|
||||
<field name="model">project.task.type</field>
|
||||
<field name="inherit_id" ref="project.task_type_edit"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="mail_template_id" position="after">
|
||||
<field name="approval_template_id"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Loading…
Reference in a new issue