odoo-addon-mail-gateway-whatsapp 16.0.1.0.0.11__py3-none-any.whl → 16.0.1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- odoo/addons/mail_gateway_whatsapp/README.rst +13 -1
- odoo/addons/mail_gateway_whatsapp/__init__.py +1 -0
- odoo/addons/mail_gateway_whatsapp/__manifest__.py +4 -1
- odoo/addons/mail_gateway_whatsapp/i18n/mail_gateway_whatsapp.pot +590 -0
- odoo/addons/mail_gateway_whatsapp/models/__init__.py +1 -0
- odoo/addons/mail_gateway_whatsapp/models/mail_gateway.py +61 -1
- odoo/addons/mail_gateway_whatsapp/models/mail_gateway_whatsapp.py +24 -3
- odoo/addons/mail_gateway_whatsapp/models/mail_whatsapp_template.py +193 -0
- odoo/addons/mail_gateway_whatsapp/readme/CONFIGURE.rst +1 -0
- odoo/addons/mail_gateway_whatsapp/readme/CONTRIBUTORS.rst +3 -0
- odoo/addons/mail_gateway_whatsapp/readme/ROADMAP.rst +4 -0
- odoo/addons/mail_gateway_whatsapp/security/ir.model.access.csv +2 -0
- odoo/addons/mail_gateway_whatsapp/security/security.xml +8 -0
- odoo/addons/mail_gateway_whatsapp/static/description/index.html +41 -19
- odoo/addons/mail_gateway_whatsapp/tests/__init__.py +1 -0
- odoo/addons/mail_gateway_whatsapp/tests/test_mail_gateway_whatsapp.py +75 -1
- odoo/addons/mail_gateway_whatsapp/tests/test_mail_whatsapp_template.py +170 -0
- odoo/addons/mail_gateway_whatsapp/tools/__init__.py +1 -0
- odoo/addons/mail_gateway_whatsapp/tools/const.py +75 -0
- odoo/addons/mail_gateway_whatsapp/views/mail_gateway.xml +28 -0
- odoo/addons/mail_gateway_whatsapp/views/mail_whatsapp_template_views.xml +154 -0
- odoo/addons/mail_gateway_whatsapp/wizards/__init__.py +1 -0
- odoo/addons/mail_gateway_whatsapp/wizards/mail_compose_gateway_message.py +30 -0
- odoo/addons/mail_gateway_whatsapp/wizards/mail_compose_gateway_message.xml +31 -0
- odoo/addons/mail_gateway_whatsapp/wizards/whatsapp_composer.py +53 -1
- odoo/addons/mail_gateway_whatsapp/wizards/whatsapp_composer.xml +11 -1
- {odoo_addon_mail_gateway_whatsapp-16.0.1.0.0.11.dist-info → odoo_addon_mail_gateway_whatsapp-16.0.1.1.0.dist-info}/METADATA +14 -2
- {odoo_addon_mail_gateway_whatsapp-16.0.1.0.0.11.dist-info → odoo_addon_mail_gateway_whatsapp-16.0.1.1.0.dist-info}/RECORD +30 -21
- {odoo_addon_mail_gateway_whatsapp-16.0.1.0.0.11.dist-info → odoo_addon_mail_gateway_whatsapp-16.0.1.1.0.dist-info}/WHEEL +1 -1
- {odoo_addon_mail_gateway_whatsapp-16.0.1.0.0.11.dist-info → odoo_addon_mail_gateway_whatsapp-16.0.1.1.0.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
# Copyright 2022 Creu Blanca
|
|
2
2
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
3
|
+
import requests
|
|
4
|
+
from werkzeug.urls import url_join
|
|
3
5
|
|
|
4
|
-
from odoo import fields, models
|
|
6
|
+
from odoo import _, api, fields, models
|
|
7
|
+
from odoo.exceptions import UserError
|
|
8
|
+
|
|
9
|
+
BASE_URL = "https://graph.facebook.com/"
|
|
5
10
|
|
|
6
11
|
|
|
7
12
|
class MailGateway(models.Model):
|
|
@@ -13,3 +18,58 @@ class MailGateway(models.Model):
|
|
|
13
18
|
)
|
|
14
19
|
whatsapp_from_phone = fields.Char()
|
|
15
20
|
whatsapp_version = fields.Char(default="15.0")
|
|
21
|
+
whatsapp_account_id = fields.Char()
|
|
22
|
+
whatsapp_template_ids = fields.One2many("mail.whatsapp.template", "gateway_id")
|
|
23
|
+
whatsapp_template_count = fields.Integer(compute="_compute_whatsapp_template_count")
|
|
24
|
+
|
|
25
|
+
@api.depends("whatsapp_template_ids")
|
|
26
|
+
def _compute_whatsapp_template_count(self):
|
|
27
|
+
for gateway in self:
|
|
28
|
+
gateway.whatsapp_template_count = len(gateway.whatsapp_template_ids)
|
|
29
|
+
|
|
30
|
+
def button_import_whatsapp_template(self):
|
|
31
|
+
self.ensure_one()
|
|
32
|
+
WhatsappTemplate = self.env["mail.whatsapp.template"]
|
|
33
|
+
if not self.whatsapp_account_id:
|
|
34
|
+
raise UserError(_("WhatsApp Account is required to import templates."))
|
|
35
|
+
meta_info = {}
|
|
36
|
+
template_url = url_join(
|
|
37
|
+
BASE_URL,
|
|
38
|
+
f"v{self.whatsapp_version}/{self.whatsapp_account_id}/message_templates",
|
|
39
|
+
)
|
|
40
|
+
try:
|
|
41
|
+
meta_request = requests.get(
|
|
42
|
+
template_url,
|
|
43
|
+
headers={"Authorization": f"Bearer {self.token}"},
|
|
44
|
+
timeout=10,
|
|
45
|
+
)
|
|
46
|
+
meta_request.raise_for_status()
|
|
47
|
+
meta_info = meta_request.json()
|
|
48
|
+
except Exception as err:
|
|
49
|
+
raise UserError(str(err)) from err
|
|
50
|
+
current_templates = WhatsappTemplate.with_context(active_test=False).search(
|
|
51
|
+
[("gateway_id", "=", self.id)]
|
|
52
|
+
)
|
|
53
|
+
templates_by_id = {t.template_uid: t for t in current_templates}
|
|
54
|
+
create_vals = []
|
|
55
|
+
for template_data in meta_info.get("data", []):
|
|
56
|
+
ws_template = templates_by_id.get(template_data["id"])
|
|
57
|
+
if ws_template:
|
|
58
|
+
ws_template.write(
|
|
59
|
+
WhatsappTemplate._prepare_values_to_import(self, template_data)
|
|
60
|
+
)
|
|
61
|
+
else:
|
|
62
|
+
create_vals.append(
|
|
63
|
+
WhatsappTemplate._prepare_values_to_import(self, template_data)
|
|
64
|
+
)
|
|
65
|
+
WhatsappTemplate.create(create_vals)
|
|
66
|
+
return {
|
|
67
|
+
"type": "ir.actions.client",
|
|
68
|
+
"tag": "display_notification",
|
|
69
|
+
"params": {
|
|
70
|
+
"title": _("WathsApp Templates"),
|
|
71
|
+
"type": "success",
|
|
72
|
+
"message": _("Synchronization successfully."),
|
|
73
|
+
"next": {"type": "ir.actions.act_window_close"},
|
|
74
|
+
},
|
|
75
|
+
}
|
|
@@ -285,14 +285,35 @@ class MailGatewayWhatsappService(models.AbstractModel):
|
|
|
285
285
|
def _send_payload(
|
|
286
286
|
self, channel, body=False, media_id=False, media_type=False, media_name=False
|
|
287
287
|
):
|
|
288
|
+
whatsapp_template = self.env["mail.whatsapp.template"]
|
|
289
|
+
if self.env.context.get("whatsapp_template_id"):
|
|
290
|
+
whatsapp_template = self.env["mail.whatsapp.template"].browse(
|
|
291
|
+
self.env.context.get("whatsapp_template_id")
|
|
292
|
+
)
|
|
288
293
|
if body:
|
|
289
|
-
|
|
294
|
+
payload = {
|
|
290
295
|
"messaging_product": "whatsapp",
|
|
291
296
|
"recipient_type": "individual",
|
|
292
297
|
"to": channel.gateway_channel_token,
|
|
293
|
-
"type": "text",
|
|
294
|
-
"text": {"preview_url": False, "body": html2plaintext(body)},
|
|
295
298
|
}
|
|
299
|
+
if whatsapp_template:
|
|
300
|
+
payload.update(
|
|
301
|
+
{
|
|
302
|
+
"type": "template",
|
|
303
|
+
"template": {
|
|
304
|
+
"name": whatsapp_template.template_name,
|
|
305
|
+
"language": {"code": whatsapp_template.language},
|
|
306
|
+
},
|
|
307
|
+
}
|
|
308
|
+
)
|
|
309
|
+
else:
|
|
310
|
+
payload.update(
|
|
311
|
+
{
|
|
312
|
+
"type": "text",
|
|
313
|
+
"text": {"preview_url": False, "body": html2plaintext(body)},
|
|
314
|
+
}
|
|
315
|
+
)
|
|
316
|
+
return payload
|
|
296
317
|
if media_id:
|
|
297
318
|
media_data = {"id": media_id}
|
|
298
319
|
if media_type == "document":
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Copyright 2024 Tecnativa - Carlos López
|
|
2
|
+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
from werkzeug.urls import url_join
|
|
7
|
+
|
|
8
|
+
from odoo import api, fields, models
|
|
9
|
+
from odoo.exceptions import UserError
|
|
10
|
+
from odoo.tools import ustr
|
|
11
|
+
|
|
12
|
+
from odoo.addons.http_routing.models.ir_http import slugify
|
|
13
|
+
|
|
14
|
+
from ..tools.const import supported_languages
|
|
15
|
+
from .mail_gateway import BASE_URL
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MailWhatsAppTemplate(models.Model):
|
|
19
|
+
_name = "mail.whatsapp.template"
|
|
20
|
+
_description = "Mail WhatsApp template"
|
|
21
|
+
|
|
22
|
+
name = fields.Char(required=True)
|
|
23
|
+
body = fields.Text(required=True)
|
|
24
|
+
header = fields.Char()
|
|
25
|
+
footer = fields.Char()
|
|
26
|
+
template_name = fields.Char(
|
|
27
|
+
compute="_compute_template_name", store=True, copy=False
|
|
28
|
+
)
|
|
29
|
+
is_supported = fields.Boolean(copy=False)
|
|
30
|
+
template_uid = fields.Char(readonly=True, copy=False)
|
|
31
|
+
category = fields.Selection(
|
|
32
|
+
[
|
|
33
|
+
("authentication", "Authentication"),
|
|
34
|
+
("marketing", "Marketing"),
|
|
35
|
+
("utility", "Utility"),
|
|
36
|
+
],
|
|
37
|
+
required=True,
|
|
38
|
+
)
|
|
39
|
+
state = fields.Selection(
|
|
40
|
+
[
|
|
41
|
+
("draft", "Draft"),
|
|
42
|
+
("pending", "Pending"),
|
|
43
|
+
("approved", "Approved"),
|
|
44
|
+
("in_appeal", "In Appeal"),
|
|
45
|
+
("rejected", "Rejected"),
|
|
46
|
+
("pending_deletion", "Pending Deletion"),
|
|
47
|
+
("deleted", "Deleted"),
|
|
48
|
+
("disabled", "Disabled"),
|
|
49
|
+
("paused", "Paused"),
|
|
50
|
+
("limit_exceeded", "Limit Exceeded"),
|
|
51
|
+
("archived", "Archived"),
|
|
52
|
+
],
|
|
53
|
+
default="draft",
|
|
54
|
+
required=True,
|
|
55
|
+
)
|
|
56
|
+
language = fields.Selection(supported_languages, required=True)
|
|
57
|
+
gateway_id = fields.Many2one(
|
|
58
|
+
"mail.gateway",
|
|
59
|
+
domain=[("gateway_type", "=", "whatsapp")],
|
|
60
|
+
required=True,
|
|
61
|
+
ondelete="cascade",
|
|
62
|
+
)
|
|
63
|
+
company_id = fields.Many2one(
|
|
64
|
+
"res.company", related="gateway_id.company_id", store=True
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
_sql_constraints = [
|
|
68
|
+
(
|
|
69
|
+
"unique_name_gateway_id",
|
|
70
|
+
"unique(name, language, gateway_id)",
|
|
71
|
+
"Duplicate name is not allowed for Gateway.",
|
|
72
|
+
)
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
@api.depends("name", "state", "template_uid")
|
|
76
|
+
def _compute_template_name(self):
|
|
77
|
+
for template in self:
|
|
78
|
+
if not template.template_name or (
|
|
79
|
+
template.state == "draft" and not template.template_uid
|
|
80
|
+
):
|
|
81
|
+
template.template_name = re.sub(
|
|
82
|
+
r"\W+", "_", slugify(template.name or "")
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def button_back2draft(self):
|
|
86
|
+
self.write({"state": "draft"})
|
|
87
|
+
|
|
88
|
+
def button_export_template(self):
|
|
89
|
+
self.ensure_one()
|
|
90
|
+
gateway = self.gateway_id
|
|
91
|
+
template_url = url_join(
|
|
92
|
+
BASE_URL,
|
|
93
|
+
f"v{gateway.whatsapp_version}/{gateway.whatsapp_account_id}/message_templates",
|
|
94
|
+
)
|
|
95
|
+
try:
|
|
96
|
+
payload = self._prepare_values_to_export()
|
|
97
|
+
response = requests.post(
|
|
98
|
+
template_url,
|
|
99
|
+
headers={"Authorization": "Bearer %s" % gateway.token},
|
|
100
|
+
json=payload,
|
|
101
|
+
timeout=10,
|
|
102
|
+
)
|
|
103
|
+
response.raise_for_status()
|
|
104
|
+
json_data = response.json()
|
|
105
|
+
self.write(
|
|
106
|
+
{
|
|
107
|
+
"template_uid": json_data.get("id"),
|
|
108
|
+
"state": json_data.get("status").lower(),
|
|
109
|
+
"is_supported": True,
|
|
110
|
+
}
|
|
111
|
+
)
|
|
112
|
+
except requests.exceptions.HTTPError as ex:
|
|
113
|
+
msj = f"{ustr(ex)} \n{ex.response.text}"
|
|
114
|
+
raise UserError(msj) from ex
|
|
115
|
+
except Exception as err:
|
|
116
|
+
raise UserError(ustr(err)) from err
|
|
117
|
+
|
|
118
|
+
def _prepare_values_to_export(self):
|
|
119
|
+
components = self._prepare_components_to_export()
|
|
120
|
+
return {
|
|
121
|
+
"name": self.template_name,
|
|
122
|
+
"category": self.category.upper(),
|
|
123
|
+
"language": self.language,
|
|
124
|
+
"components": components,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
def _prepare_components_to_export(self):
|
|
128
|
+
components = [{"type": "BODY", "text": self.body}]
|
|
129
|
+
if self.header:
|
|
130
|
+
components.append(
|
|
131
|
+
{
|
|
132
|
+
"type": "HEADER",
|
|
133
|
+
"format": "text",
|
|
134
|
+
"text": self.header,
|
|
135
|
+
}
|
|
136
|
+
)
|
|
137
|
+
if self.footer:
|
|
138
|
+
components.append(
|
|
139
|
+
{
|
|
140
|
+
"type": "FOOTER",
|
|
141
|
+
"text": self.footer,
|
|
142
|
+
}
|
|
143
|
+
)
|
|
144
|
+
# TODO: add more components(buttons, location, etc)
|
|
145
|
+
return components
|
|
146
|
+
|
|
147
|
+
def button_sync_template(self):
|
|
148
|
+
self.ensure_one()
|
|
149
|
+
gateway = self.gateway_id
|
|
150
|
+
template_url = url_join(
|
|
151
|
+
BASE_URL,
|
|
152
|
+
f"{self.template_uid}",
|
|
153
|
+
)
|
|
154
|
+
try:
|
|
155
|
+
response = requests.get(
|
|
156
|
+
template_url,
|
|
157
|
+
headers={"Authorization": "Bearer %s" % gateway.token},
|
|
158
|
+
timeout=10,
|
|
159
|
+
)
|
|
160
|
+
response.raise_for_status()
|
|
161
|
+
json_data = response.json()
|
|
162
|
+
vals = self._prepare_values_to_import(gateway, json_data)
|
|
163
|
+
self.write(vals)
|
|
164
|
+
except Exception as err:
|
|
165
|
+
raise UserError(str(err)) from err
|
|
166
|
+
return {
|
|
167
|
+
"type": "ir.actions.client",
|
|
168
|
+
"tag": "reload",
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@api.model
|
|
172
|
+
def _prepare_values_to_import(self, gateway, json_data):
|
|
173
|
+
vals = {
|
|
174
|
+
"name": json_data.get("name").replace("_", " ").title(),
|
|
175
|
+
"template_name": json_data.get("name"),
|
|
176
|
+
"category": json_data.get("category").lower(),
|
|
177
|
+
"language": json_data.get("language"),
|
|
178
|
+
"state": json_data.get("status").lower(),
|
|
179
|
+
"template_uid": json_data.get("id"),
|
|
180
|
+
"gateway_id": gateway.id,
|
|
181
|
+
}
|
|
182
|
+
is_supported = True
|
|
183
|
+
for component in json_data.get("components", []):
|
|
184
|
+
if component["type"] == "HEADER" and component["format"] == "TEXT":
|
|
185
|
+
vals["header"] = component["text"]
|
|
186
|
+
elif component["type"] == "BODY":
|
|
187
|
+
vals["body"] = component["text"]
|
|
188
|
+
elif component["type"] == "FOOTER":
|
|
189
|
+
vals["footer"] = component["text"]
|
|
190
|
+
else:
|
|
191
|
+
is_supported = False
|
|
192
|
+
vals["is_supported"] = is_supported
|
|
193
|
+
return vals
|
|
@@ -19,6 +19,7 @@ In order to make it you must follow this steps:
|
|
|
19
19
|
|
|
20
20
|
* Use the Meta App authentication key as `Token` field
|
|
21
21
|
* Use the Meta App Phone Number ID as `Whatsapp from Phone` field
|
|
22
|
+
* Use the Meta Account Business ID as `Whatsapp account` field (only if you need sync templates)
|
|
22
23
|
* Write your own `Webhook key`
|
|
23
24
|
* Use the Application Secret Key on `Whatsapp Security Key`. It will be used in order to validate the data
|
|
24
25
|
* Press the `Integrate Webhook Key`. In this case, it will not integrate it, we need to make it manually
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
|
2
2
|
access_whatsapp_composer,access.whatsapp.composer,model_whatsapp_composer,base.group_user,1,1,1,0
|
|
3
|
+
access_mail_whatsapp_template_group_system,mail_whatsapp_template_group_system,model_mail_whatsapp_template,base.group_system,1,1,1,1
|
|
4
|
+
access_mail_whatsapp_template_group_user,mail_whatsapp_template_group_user,model_mail_whatsapp_template,base.group_user,1,0,0,0
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
|
2
|
+
<odoo noupdate="1">
|
|
3
|
+
<record id="mail_whatsapp_template_rule" model="ir.rule">
|
|
4
|
+
<field name="name">WhatsApp template: multicompany</field>
|
|
5
|
+
<field name="model_id" ref="model_mail_whatsapp_template" />
|
|
6
|
+
<field name="domain_force">[('company_id', 'in', company_ids + [False])]</field>
|
|
7
|
+
</record>
|
|
8
|
+
</odoo>
|
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
|
|
9
9
|
/*
|
|
10
10
|
:Author: David Goodger (goodger@python.org)
|
|
11
|
-
:Id: $Id: html4css1.css
|
|
11
|
+
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
|
|
12
12
|
:Copyright: This stylesheet has been placed in the public domain.
|
|
13
13
|
|
|
14
14
|
Default cascading style sheet for the HTML output of Docutils.
|
|
15
|
+
Despite the name, some widely supported CSS2 features are used.
|
|
15
16
|
|
|
16
17
|
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
|
|
17
18
|
customize this style sheet.
|
|
@@ -274,7 +275,7 @@ pre.literal-block, pre.doctest-block, pre.math, pre.code {
|
|
|
274
275
|
margin-left: 2em ;
|
|
275
276
|
margin-right: 2em }
|
|
276
277
|
|
|
277
|
-
pre.code .ln { color:
|
|
278
|
+
pre.code .ln { color: gray; } /* line numbers */
|
|
278
279
|
pre.code, code { background-color: #eeeeee }
|
|
279
280
|
pre.code .comment, code .comment { color: #5C6576 }
|
|
280
281
|
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
|
|
@@ -300,7 +301,7 @@ span.option {
|
|
|
300
301
|
span.pre {
|
|
301
302
|
white-space: pre }
|
|
302
303
|
|
|
303
|
-
span.problematic {
|
|
304
|
+
span.problematic, pre.problematic {
|
|
304
305
|
color: red }
|
|
305
306
|
|
|
306
307
|
span.section-subtitle {
|
|
@@ -366,7 +367,7 @@ ul.auto-toc {
|
|
|
366
367
|
!! This file is generated by oca-gen-addon-readme !!
|
|
367
368
|
!! changes will be overwritten. !!
|
|
368
369
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
369
|
-
!! source digest: sha256:
|
|
370
|
+
!! source digest: sha256:70475641a2c7498a1ee67811eadb397e33c33435cdd2efb8edce729026ad09c4
|
|
370
371
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
|
|
371
372
|
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/social/tree/16.0/mail_gateway_whatsapp"><img alt="OCA/social" src="https://img.shields.io/badge/github-OCA%2Fsocial-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/social-16-0/social-16-0-mail_gateway_whatsapp"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/social&target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
|
|
372
373
|
<p>This module allows to respond whatsapp chats.</p>
|
|
@@ -381,12 +382,13 @@ of partners in an integrated way.</p>
|
|
|
381
382
|
</ul>
|
|
382
383
|
</li>
|
|
383
384
|
<li><a class="reference internal" href="#usage" id="toc-entry-4">Usage</a></li>
|
|
384
|
-
<li><a class="reference internal" href="#
|
|
385
|
-
<li><a class="reference internal" href="#
|
|
386
|
-
<li><a class="reference internal" href="#
|
|
387
|
-
<li><a class="reference internal" href="#
|
|
388
|
-
<li><a class="reference internal" href="#
|
|
389
|
-
<li><a class="reference internal" href="#
|
|
385
|
+
<li><a class="reference internal" href="#known-issues-roadmap" id="toc-entry-5">Known issues / Roadmap</a></li>
|
|
386
|
+
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-6">Bug Tracker</a></li>
|
|
387
|
+
<li><a class="reference internal" href="#credits" id="toc-entry-7">Credits</a><ul>
|
|
388
|
+
<li><a class="reference internal" href="#authors" id="toc-entry-8">Authors</a></li>
|
|
389
|
+
<li><a class="reference internal" href="#contributors" id="toc-entry-9">Contributors</a></li>
|
|
390
|
+
<li><a class="reference internal" href="#other-credits" id="toc-entry-10">Other credits</a></li>
|
|
391
|
+
<li><a class="reference internal" href="#maintainers" id="toc-entry-11">Maintainers</a></li>
|
|
390
392
|
</ul>
|
|
391
393
|
</li>
|
|
392
394
|
</ul>
|
|
@@ -412,6 +414,7 @@ In order to make it you must follow this steps:</p>
|
|
|
412
414
|
<ul class="simple">
|
|
413
415
|
<li>Use the Meta App authentication key as <cite>Token</cite> field</li>
|
|
414
416
|
<li>Use the Meta App Phone Number ID as <cite>Whatsapp from Phone</cite> field</li>
|
|
417
|
+
<li>Use the Meta Account Business ID as <cite>Whatsapp account</cite> field (only if you need sync templates)</li>
|
|
415
418
|
<li>Write your own <cite>Webhook key</cite></li>
|
|
416
419
|
<li>Use the Application Secret Key on <cite>Whatsapp Security Key</cite>. It will be used in order to validate the data</li>
|
|
417
420
|
<li>Press the <cite>Integrate Webhook Key</cite>. In this case, it will not integrate it, we need to make it manually</li>
|
|
@@ -434,8 +437,16 @@ In order to make it you must follow this steps:</p>
|
|
|
434
437
|
<li>Now you will be able to respond and receive messages to this person.</li>
|
|
435
438
|
</ol>
|
|
436
439
|
</div>
|
|
440
|
+
<div class="section" id="known-issues-roadmap">
|
|
441
|
+
<h1><a class="toc-backref" href="#toc-entry-5">Known issues / Roadmap</a></h1>
|
|
442
|
+
<p><strong>WhatsApp templates</strong></p>
|
|
443
|
+
<ul class="simple">
|
|
444
|
+
<li>Add support for <cite>Variables</cite></li>
|
|
445
|
+
<li>Add support for <cite>Buttons</cite></li>
|
|
446
|
+
</ul>
|
|
447
|
+
</div>
|
|
437
448
|
<div class="section" id="bug-tracker">
|
|
438
|
-
<h1><a class="toc-backref" href="#toc-entry-
|
|
449
|
+
<h1><a class="toc-backref" href="#toc-entry-6">Bug Tracker</a></h1>
|
|
439
450
|
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/social/issues">GitHub Issues</a>.
|
|
440
451
|
In case of trouble, please check there if your issue has already been reported.
|
|
441
452
|
If you spotted it first, help us to smash it by providing a detailed and welcomed
|
|
@@ -443,29 +454,40 @@ If you spotted it first, help us to smash it by providing a detailed and welcome
|
|
|
443
454
|
<p>Do not contact contributors directly about support or help with technical issues.</p>
|
|
444
455
|
</div>
|
|
445
456
|
<div class="section" id="credits">
|
|
446
|
-
<h1><a class="toc-backref" href="#toc-entry-
|
|
457
|
+
<h1><a class="toc-backref" href="#toc-entry-7">Credits</a></h1>
|
|
447
458
|
<div class="section" id="authors">
|
|
448
|
-
<h2><a class="toc-backref" href="#toc-entry-
|
|
459
|
+
<h2><a class="toc-backref" href="#toc-entry-8">Authors</a></h2>
|
|
449
460
|
<ul class="simple">
|
|
450
461
|
<li>Creu Blanca</li>
|
|
451
462
|
<li>Dixmit</li>
|
|
452
463
|
</ul>
|
|
453
464
|
</div>
|
|
454
465
|
<div class="section" id="contributors">
|
|
455
|
-
<h2><a class="toc-backref" href="#toc-entry-
|
|
466
|
+
<h2><a class="toc-backref" href="#toc-entry-9">Contributors</a></h2>
|
|
467
|
+
<ul>
|
|
468
|
+
<li><p class="first">Olga Marco <<a class="reference external" href="mailto:olga.marco@creublanca.es">olga.marco@creublanca.es</a>></p>
|
|
469
|
+
</li>
|
|
470
|
+
<li><p class="first">Enric Tobella <<a class="reference external" href="mailto:etobella@creublanca.es">etobella@creublanca.es</a>></p>
|
|
471
|
+
</li>
|
|
472
|
+
<li><p class="first"><a class="reference external" href="https://www.tecnativa.com">Tecnativa</a>:</p>
|
|
473
|
+
<blockquote>
|
|
456
474
|
<ul class="simple">
|
|
457
|
-
<li>
|
|
458
|
-
|
|
475
|
+
<li>Carlos Lopez</li>
|
|
476
|
+
</ul>
|
|
477
|
+
</blockquote>
|
|
478
|
+
</li>
|
|
459
479
|
</ul>
|
|
460
480
|
</div>
|
|
461
481
|
<div class="section" id="other-credits">
|
|
462
|
-
<h2><a class="toc-backref" href="#toc-entry-
|
|
482
|
+
<h2><a class="toc-backref" href="#toc-entry-10">Other credits</a></h2>
|
|
463
483
|
<p>This work has been funded by AEOdoo (Asociación Española de Odoo - <a class="reference external" href="https://www.aeodoo.org">https://www.aeodoo.org</a>)</p>
|
|
464
484
|
</div>
|
|
465
485
|
<div class="section" id="maintainers">
|
|
466
|
-
<h2><a class="toc-backref" href="#toc-entry-
|
|
486
|
+
<h2><a class="toc-backref" href="#toc-entry-11">Maintainers</a></h2>
|
|
467
487
|
<p>This module is maintained by the OCA.</p>
|
|
468
|
-
<a class="reference external image-reference" href="https://odoo-community.org"
|
|
488
|
+
<a class="reference external image-reference" href="https://odoo-community.org">
|
|
489
|
+
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
|
|
490
|
+
</a>
|
|
469
491
|
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
|
|
470
492
|
mission is to support the collaborative development of Odoo features and
|
|
471
493
|
promote its widespread use.</p>
|
|
@@ -6,7 +6,10 @@ import hmac
|
|
|
6
6
|
import json
|
|
7
7
|
from unittest.mock import MagicMock, patch
|
|
8
8
|
|
|
9
|
+
from markupsafe import Markup
|
|
10
|
+
|
|
9
11
|
from odoo.exceptions import UserError
|
|
12
|
+
from odoo.tests import Form, RecordCapturer
|
|
10
13
|
from odoo.tests.common import tagged
|
|
11
14
|
from odoo.tools import mute_logger
|
|
12
15
|
|
|
@@ -14,7 +17,7 @@ from odoo.addons.mail_gateway.tests.common import MailGatewayTestCase
|
|
|
14
17
|
|
|
15
18
|
|
|
16
19
|
@tagged("-at_install", "post_install")
|
|
17
|
-
class
|
|
20
|
+
class TestMailGatewayWhatsApp(MailGatewayTestCase):
|
|
18
21
|
@classmethod
|
|
19
22
|
def setUpClass(cls):
|
|
20
23
|
super().setUpClass()
|
|
@@ -26,6 +29,18 @@ class TestMailGatewayTelegram(MailGatewayTestCase):
|
|
|
26
29
|
"token": "token",
|
|
27
30
|
"whatsapp_security_key": "key",
|
|
28
31
|
"webhook_secret": "MY-SECRET",
|
|
32
|
+
"member_ids": [(4, cls.env.user.id)],
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
cls.ws_template = cls.env["mail.whatsapp.template"].create(
|
|
36
|
+
{
|
|
37
|
+
"name": "New template",
|
|
38
|
+
"category": "marketing",
|
|
39
|
+
"language": "es",
|
|
40
|
+
"body": "Demo template",
|
|
41
|
+
"state": "approved",
|
|
42
|
+
"is_supported": True,
|
|
43
|
+
"gateway_id": cls.gateway.id,
|
|
29
44
|
}
|
|
30
45
|
)
|
|
31
46
|
cls.partner = cls.env["res.partner"].create(
|
|
@@ -279,6 +294,65 @@ class TestMailGatewayTelegram(MailGatewayTestCase):
|
|
|
279
294
|
)
|
|
280
295
|
self.assertEqual(message.notification_ids.notification_status, "exception")
|
|
281
296
|
|
|
297
|
+
def test_send_message_text(self):
|
|
298
|
+
"""
|
|
299
|
+
Test that the message is sent correctly
|
|
300
|
+
- First message need a template
|
|
301
|
+
- Second message does not need a template
|
|
302
|
+
"""
|
|
303
|
+
ctx = {
|
|
304
|
+
"default_res_model": self.partner._name,
|
|
305
|
+
"default_res_id": self.partner.id,
|
|
306
|
+
"default_number_field_name": "mobile",
|
|
307
|
+
"default_composition_mode": "comment",
|
|
308
|
+
"default_gateway_id": self.gateway.id,
|
|
309
|
+
}
|
|
310
|
+
self.gateway.whatsapp_account_id = "123456"
|
|
311
|
+
form_composer = Form(self.env["whatsapp.composer"].with_context(**ctx))
|
|
312
|
+
form_composer.body = "Body test"
|
|
313
|
+
self.assertTrue(form_composer.is_required_template)
|
|
314
|
+
self.assertTrue(form_composer._get_modifier("template_id", "required"))
|
|
315
|
+
form_composer.template_id = self.ws_template
|
|
316
|
+
composer = form_composer.save()
|
|
317
|
+
self.assertEqual(composer.body, "Demo template")
|
|
318
|
+
channel = self.partner._whatsapp_get_channel(
|
|
319
|
+
composer.number_field_name, composer.gateway_id
|
|
320
|
+
)
|
|
321
|
+
message_domain = [
|
|
322
|
+
("gateway_type", "=", "whatsapp"),
|
|
323
|
+
("model", "=", channel._name),
|
|
324
|
+
("res_id", "=", channel.id),
|
|
325
|
+
]
|
|
326
|
+
with RecordCapturer(self.env["mail.message"], message_domain) as capture, patch(
|
|
327
|
+
"requests.post"
|
|
328
|
+
) as post_mock:
|
|
329
|
+
post_mock.return_value = MagicMock()
|
|
330
|
+
composer.action_send_whatsapp()
|
|
331
|
+
self.assertEqual(len(capture.records), 1)
|
|
332
|
+
self.assertEqual(capture.records.body, Markup("<p>Demo template</p>"))
|
|
333
|
+
# second message
|
|
334
|
+
form_composer = Form(self.env["whatsapp.composer"].with_context(**ctx))
|
|
335
|
+
form_composer.body = "Body test"
|
|
336
|
+
self.assertFalse(form_composer.is_required_template)
|
|
337
|
+
self.assertFalse(form_composer._get_modifier("template_id", "required"))
|
|
338
|
+
composer = form_composer.save()
|
|
339
|
+
self.assertEqual(composer.body, "Body test")
|
|
340
|
+
channel = self.partner._whatsapp_get_channel(
|
|
341
|
+
composer.number_field_name, composer.gateway_id
|
|
342
|
+
)
|
|
343
|
+
message_domain = [
|
|
344
|
+
("gateway_type", "=", "whatsapp"),
|
|
345
|
+
("model", "=", channel._name),
|
|
346
|
+
("res_id", "=", channel.id),
|
|
347
|
+
]
|
|
348
|
+
with RecordCapturer(self.env["mail.message"], message_domain) as capture, patch(
|
|
349
|
+
"requests.post"
|
|
350
|
+
) as post_mock:
|
|
351
|
+
post_mock.return_value = MagicMock()
|
|
352
|
+
composer.action_send_whatsapp()
|
|
353
|
+
self.assertEqual(len(capture.records), 1)
|
|
354
|
+
self.assertEqual(capture.records.body, Markup("<p>Body test</p>"))
|
|
355
|
+
|
|
282
356
|
def test_compose(self):
|
|
283
357
|
self.gateway.webhook_key = self.webhook
|
|
284
358
|
self.gateway.set_webhook()
|