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
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Copyright 2024 Tecnativa - Carlos López
|
|
2
|
+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from unittest.mock import patch
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
|
|
9
|
+
from odoo.exceptions import UserError
|
|
10
|
+
from odoo.tests.common import tagged
|
|
11
|
+
|
|
12
|
+
from odoo.addons.mail_gateway.tests.common import MailGatewayTestCase
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@tagged("-at_install", "post_install")
|
|
16
|
+
class TestMailWhatsAppTemplate(MailGatewayTestCase):
|
|
17
|
+
@classmethod
|
|
18
|
+
def setUpClass(cls):
|
|
19
|
+
super().setUpClass()
|
|
20
|
+
cls.gateway = cls.env["mail.gateway"].create(
|
|
21
|
+
{
|
|
22
|
+
"name": "gateway",
|
|
23
|
+
"gateway_type": "whatsapp",
|
|
24
|
+
"token": "token",
|
|
25
|
+
"whatsapp_security_key": "key",
|
|
26
|
+
"webhook_secret": "MY-SECRET",
|
|
27
|
+
"member_ids": [(4, cls.env.user.id)],
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
cls.new_template_response_data = {
|
|
31
|
+
"id": "018273645",
|
|
32
|
+
"status": "APPROVED",
|
|
33
|
+
}
|
|
34
|
+
cls.new_template_full_response_data = {
|
|
35
|
+
"name": "new_template",
|
|
36
|
+
"parameter_format": "POSITIONAL",
|
|
37
|
+
"components": [
|
|
38
|
+
{"type": "HEADER", "format": "TEXT", "text": "Header 1"},
|
|
39
|
+
{"type": "BODY", "text": "Body 1"},
|
|
40
|
+
{"type": "FOOTER", "text": "Footer changed"},
|
|
41
|
+
],
|
|
42
|
+
"language": "es",
|
|
43
|
+
"status": "APPROVED",
|
|
44
|
+
"category": "MARKETING",
|
|
45
|
+
"id": "018273645",
|
|
46
|
+
}
|
|
47
|
+
cls.template_1_data = {
|
|
48
|
+
"name": "test_odoo_1",
|
|
49
|
+
"parameter_format": "POSITIONAL",
|
|
50
|
+
"components": [
|
|
51
|
+
{"type": "HEADER", "format": "TEXT", "text": "Header 1"},
|
|
52
|
+
{"type": "BODY", "text": "Body 1"},
|
|
53
|
+
{"type": "FOOTER", "text": "Footer 1"},
|
|
54
|
+
],
|
|
55
|
+
"language": "es",
|
|
56
|
+
"status": "APPROVED",
|
|
57
|
+
"category": "MARKETING",
|
|
58
|
+
"id": "1234567890",
|
|
59
|
+
}
|
|
60
|
+
cls.template_2_data = {
|
|
61
|
+
"name": "test_with_buttons",
|
|
62
|
+
"parameter_format": "POSITIONAL",
|
|
63
|
+
"components": [
|
|
64
|
+
{"type": "HEADER", "format": "TEXT", "text": "Header 2"},
|
|
65
|
+
{"type": "BODY", "text": "Body 2"},
|
|
66
|
+
{
|
|
67
|
+
"type": "BUTTONS",
|
|
68
|
+
"buttons": [{"type": "QUICK_REPLY", "text": "Button 1"}],
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
"language": "es",
|
|
72
|
+
"status": "APPROVED",
|
|
73
|
+
"category": "MARKETING",
|
|
74
|
+
"sub_category": "CUSTOM",
|
|
75
|
+
"id": "0987654321",
|
|
76
|
+
}
|
|
77
|
+
cls.templates_download = {
|
|
78
|
+
"data": [cls.template_1_data, cls.template_2_data],
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
def _make_meta_requests(self, url, json_data, status_code=200):
|
|
82
|
+
"""
|
|
83
|
+
Simulate a fake request to the Meta API:
|
|
84
|
+
:param json_data: Dictionary with the json data to return
|
|
85
|
+
:param status_code: Status code expected
|
|
86
|
+
:returns requests.Response object
|
|
87
|
+
"""
|
|
88
|
+
response = requests.Response()
|
|
89
|
+
response.status_code = status_code
|
|
90
|
+
response._content = json.dumps(json_data).encode()
|
|
91
|
+
response.url = url
|
|
92
|
+
response.headers["Content-Type"] = "application/json"
|
|
93
|
+
return response
|
|
94
|
+
|
|
95
|
+
def test_download_templates(self):
|
|
96
|
+
def _patch_request_post(url, *args, **kwargs):
|
|
97
|
+
if "message_templates" in url:
|
|
98
|
+
return self._make_meta_requests(url, self.templates_download)
|
|
99
|
+
return original_get(url, *args, **kwargs)
|
|
100
|
+
|
|
101
|
+
with self.assertRaisesRegex(
|
|
102
|
+
UserError, "WhatsApp Account is required to import templates"
|
|
103
|
+
):
|
|
104
|
+
self.gateway.button_import_whatsapp_template()
|
|
105
|
+
self.gateway.whatsapp_account_id = "123456"
|
|
106
|
+
original_get = requests.get
|
|
107
|
+
with patch.object(requests, "get", _patch_request_post):
|
|
108
|
+
self.gateway.button_import_whatsapp_template()
|
|
109
|
+
self.assertEqual(self.gateway.whatsapp_template_count, 2)
|
|
110
|
+
template_1 = self.gateway.whatsapp_template_ids.filtered(
|
|
111
|
+
lambda t: t.template_uid == "1234567890"
|
|
112
|
+
)
|
|
113
|
+
self.assertTrue(template_1.is_supported)
|
|
114
|
+
self.assertEqual(template_1.template_name, "test_odoo_1")
|
|
115
|
+
self.assertEqual(template_1.category, "marketing")
|
|
116
|
+
self.assertEqual(template_1.language, "es")
|
|
117
|
+
self.assertEqual(template_1.state, "approved")
|
|
118
|
+
self.assertEqual(template_1.header, "Header 1")
|
|
119
|
+
self.assertEqual(template_1.body, "Body 1")
|
|
120
|
+
self.assertEqual(template_1.footer, "Footer 1")
|
|
121
|
+
template_2 = self.gateway.whatsapp_template_ids.filtered(
|
|
122
|
+
lambda t: t.template_uid == "0987654321"
|
|
123
|
+
)
|
|
124
|
+
self.assertFalse(template_2.is_supported)
|
|
125
|
+
self.assertEqual(template_2.template_name, "test_with_buttons")
|
|
126
|
+
self.assertEqual(template_2.category, "marketing")
|
|
127
|
+
self.assertEqual(template_2.language, "es")
|
|
128
|
+
self.assertEqual(template_2.state, "approved")
|
|
129
|
+
self.assertEqual(template_2.header, "Header 2")
|
|
130
|
+
self.assertEqual(template_2.body, "Body 2")
|
|
131
|
+
self.assertFalse(template_2.footer)
|
|
132
|
+
self.assertFalse(template_2.is_supported)
|
|
133
|
+
|
|
134
|
+
def test_export_template(self):
|
|
135
|
+
def _patch_request_post(url, *args, **kwargs):
|
|
136
|
+
if "message_templates" in url:
|
|
137
|
+
return self._make_meta_requests(url, self.new_template_response_data)
|
|
138
|
+
return original_post(url, *args, **kwargs)
|
|
139
|
+
|
|
140
|
+
def _patch_request_get(url, *args, **kwargs):
|
|
141
|
+
if "018273645" in url:
|
|
142
|
+
return self._make_meta_requests(
|
|
143
|
+
url, self.new_template_full_response_data
|
|
144
|
+
)
|
|
145
|
+
return original_get(url, *args, **kwargs)
|
|
146
|
+
|
|
147
|
+
original_post = requests.post
|
|
148
|
+
original_get = requests.get
|
|
149
|
+
self.gateway.whatsapp_account_id = "123456"
|
|
150
|
+
new_template = self.env["mail.whatsapp.template"].create(
|
|
151
|
+
{
|
|
152
|
+
"name": "New template",
|
|
153
|
+
"category": "marketing",
|
|
154
|
+
"language": "es",
|
|
155
|
+
"header": "Header 1",
|
|
156
|
+
"body": "Body 1",
|
|
157
|
+
"gateway_id": self.gateway.id,
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
self.assertEqual(new_template.template_name, "new_template")
|
|
161
|
+
with patch.object(requests, "post", _patch_request_post):
|
|
162
|
+
new_template.button_export_template()
|
|
163
|
+
self.assertTrue(new_template.template_uid)
|
|
164
|
+
self.assertTrue(new_template.is_supported)
|
|
165
|
+
self.assertFalse(new_template.footer)
|
|
166
|
+
self.assertEqual(new_template.state, "approved")
|
|
167
|
+
# sync templates, footer should be updated
|
|
168
|
+
with patch.object(requests, "get", _patch_request_get):
|
|
169
|
+
new_template.button_sync_template()
|
|
170
|
+
self.assertEqual(new_template.footer, "Footer changed")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from . import const
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# https://developers.facebook.com/docs/whatsapp/business-management-api/message-templates/supported-languages # noqa: B950
|
|
2
|
+
# res.lang not matching with supported languages(iso codes)
|
|
3
|
+
supported_languages = [
|
|
4
|
+
("af", "Afrikaans"),
|
|
5
|
+
("sq", "Albanian"),
|
|
6
|
+
("ar", "Arabic"),
|
|
7
|
+
("az", "Azerbaijani"),
|
|
8
|
+
("bn", "Bengali"),
|
|
9
|
+
("bg", "Bulgarian"),
|
|
10
|
+
("ca", "Catalan"),
|
|
11
|
+
("zh_CN", "Chinese (CHN)"),
|
|
12
|
+
("zh_HK", "Chinese (HKG)"),
|
|
13
|
+
("zh_TW", "Chinese (TAI)"),
|
|
14
|
+
("hr", "Croatian"),
|
|
15
|
+
("cs", "Czech"),
|
|
16
|
+
("da", "Danish"),
|
|
17
|
+
("nl", "Dutch"),
|
|
18
|
+
("en", "English"),
|
|
19
|
+
("en_GB", "English (UK)"),
|
|
20
|
+
("en_US", "English (US)"),
|
|
21
|
+
("et", "Estonian"),
|
|
22
|
+
("fil", "Filipino"),
|
|
23
|
+
("fi", "Finnish"),
|
|
24
|
+
("fr", "French"),
|
|
25
|
+
("ka", "Georgian"),
|
|
26
|
+
("de", "German"),
|
|
27
|
+
("el", "Greek"),
|
|
28
|
+
("gu", "Gujarati"),
|
|
29
|
+
("ha", "Hausa"),
|
|
30
|
+
("he", "Hebrew"),
|
|
31
|
+
("hi", "Hindi"),
|
|
32
|
+
("hu", "Hungarian"),
|
|
33
|
+
("id", "Indonesian"),
|
|
34
|
+
("ga", "Irish"),
|
|
35
|
+
("it", "Italian"),
|
|
36
|
+
("ja", "Japanese"),
|
|
37
|
+
("kn", "Kannada"),
|
|
38
|
+
("kk", "Kazakh"),
|
|
39
|
+
("rw_RW", "Kinyarwanda"),
|
|
40
|
+
("ko", "Korean"),
|
|
41
|
+
("ky_KG", "Kyrgyz (Kyrgyzstan)"),
|
|
42
|
+
("lo", "Lao"),
|
|
43
|
+
("lv", "Latvian"),
|
|
44
|
+
("lt", "Lithuanian"),
|
|
45
|
+
("mk", "Macedonian"),
|
|
46
|
+
("ms", "Malay"),
|
|
47
|
+
("ml", "Malayalam"),
|
|
48
|
+
("mr", "Marathi"),
|
|
49
|
+
("nb", "Norwegian"),
|
|
50
|
+
("fa", "Persian"),
|
|
51
|
+
("pl", "Polish"),
|
|
52
|
+
("pt_BR", "Portuguese (BR)"),
|
|
53
|
+
("pt_PT", "Portuguese (POR)"),
|
|
54
|
+
("pa", "Punjabi"),
|
|
55
|
+
("ro", "Romanian"),
|
|
56
|
+
("ru", "Russian"),
|
|
57
|
+
("sr", "Serbian"),
|
|
58
|
+
("sk", "Slovak"),
|
|
59
|
+
("sl", "Slovenian"),
|
|
60
|
+
("es", "Spanish"),
|
|
61
|
+
("es_AR", "Spanish (ARG)"),
|
|
62
|
+
("es_ES", "Spanish (SPA)"),
|
|
63
|
+
("es_MX", "Spanish (MEX)"),
|
|
64
|
+
("sw", "Swahili"),
|
|
65
|
+
("sv", "Swedish"),
|
|
66
|
+
("ta", "Tamil"),
|
|
67
|
+
("te", "Telugu"),
|
|
68
|
+
("th", "Thai"),
|
|
69
|
+
("tr", "Turkish"),
|
|
70
|
+
("uk", "Ukrainian"),
|
|
71
|
+
("ur", "Urdu"),
|
|
72
|
+
("uz", "Uzbek"),
|
|
73
|
+
("vi", "Vietnamese"),
|
|
74
|
+
("zu", "Zulu"),
|
|
75
|
+
]
|
|
@@ -7,7 +7,35 @@
|
|
|
7
7
|
<field name="model">mail.gateway</field>
|
|
8
8
|
<field name="inherit_id" ref="mail_gateway.mail_gateway_form_view" />
|
|
9
9
|
<field name="arch" type="xml">
|
|
10
|
+
<xpath expr="//header" position="inside">
|
|
11
|
+
<button
|
|
12
|
+
type="object"
|
|
13
|
+
name="button_import_whatsapp_template"
|
|
14
|
+
string="Download Templates"
|
|
15
|
+
attrs="{'invisible': [('gateway_type', '!=', 'whatsapp')]}"
|
|
16
|
+
icon="fa-download"
|
|
17
|
+
/>
|
|
18
|
+
</xpath>
|
|
19
|
+
<xpath expr="//div[@name='button_box']" position="inside">
|
|
20
|
+
<button
|
|
21
|
+
type="action"
|
|
22
|
+
name="%(mail_gateway_whatsapp.action_mail_whatsapp_template_gateway)d"
|
|
23
|
+
attrs="{'invisible': [('gateway_type', '!=', 'whatsapp')]}"
|
|
24
|
+
class="oe_stat_button"
|
|
25
|
+
icon="fa-whatsapp"
|
|
26
|
+
>
|
|
27
|
+
<field
|
|
28
|
+
name="whatsapp_template_count"
|
|
29
|
+
string="Templates"
|
|
30
|
+
widget="statinfo"
|
|
31
|
+
/>
|
|
32
|
+
</button>
|
|
33
|
+
</xpath>
|
|
10
34
|
<field name="webhook_user_id" position="after">
|
|
35
|
+
<field
|
|
36
|
+
name="whatsapp_account_id"
|
|
37
|
+
attrs="{'invisible': [('gateway_type', '!=', 'whatsapp')]}"
|
|
38
|
+
/>
|
|
11
39
|
<field
|
|
12
40
|
name="whatsapp_security_key"
|
|
13
41
|
attrs="{'invisible': [('gateway_type', '!=', 'whatsapp')]}"
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8" ?>
|
|
2
|
+
<odoo>
|
|
3
|
+
|
|
4
|
+
<record id="view_mail_whatsapp_template_tree" model="ir.ui.view">
|
|
5
|
+
<field name="name">view.mail.whatsapp.template.tree</field>
|
|
6
|
+
<field name="model">mail.whatsapp.template</field>
|
|
7
|
+
<field name="arch" type="xml">
|
|
8
|
+
<tree decoration-danger="not is_supported and template_uid">
|
|
9
|
+
<field name="name" />
|
|
10
|
+
<field name="template_name" />
|
|
11
|
+
<field name="template_uid" optional="show" />
|
|
12
|
+
<field name="category" />
|
|
13
|
+
<field name="language" />
|
|
14
|
+
<field name="gateway_id" />
|
|
15
|
+
<field
|
|
16
|
+
name="company_id"
|
|
17
|
+
options="{'no_create': True}"
|
|
18
|
+
groups="base.group_multi_company"
|
|
19
|
+
/>
|
|
20
|
+
<field
|
|
21
|
+
name="state"
|
|
22
|
+
decoration-success="state == 'approved'"
|
|
23
|
+
decoration-danger="state == 'rejected'"
|
|
24
|
+
widget="badge"
|
|
25
|
+
/>
|
|
26
|
+
<field name="is_supported" optional="show" />
|
|
27
|
+
</tree>
|
|
28
|
+
</field>
|
|
29
|
+
</record>
|
|
30
|
+
|
|
31
|
+
<record id="view_mail_whatsapp_template_form" model="ir.ui.view">
|
|
32
|
+
<field name="name">view.mail.whatsapp.template.form</field>
|
|
33
|
+
<field name="model">mail.whatsapp.template</field>
|
|
34
|
+
<field name="arch" type="xml">
|
|
35
|
+
<form>
|
|
36
|
+
<header>
|
|
37
|
+
<button
|
|
38
|
+
string="Export Template"
|
|
39
|
+
name="button_export_template"
|
|
40
|
+
type="object"
|
|
41
|
+
class="oe_highlight"
|
|
42
|
+
attrs="{'invisible': [('template_uid', '!=', False)]}"
|
|
43
|
+
/>
|
|
44
|
+
<button
|
|
45
|
+
string="Sync Template"
|
|
46
|
+
name="button_sync_template"
|
|
47
|
+
type="object"
|
|
48
|
+
attrs="{'invisible': [('template_uid', '=', False)]}"
|
|
49
|
+
/>
|
|
50
|
+
<button
|
|
51
|
+
string="Back to draft"
|
|
52
|
+
name="button_back2draft"
|
|
53
|
+
type="object"
|
|
54
|
+
attrs="{'invisible': [('state', '=', 'draft')]}"
|
|
55
|
+
/>
|
|
56
|
+
<field
|
|
57
|
+
name="state"
|
|
58
|
+
widget="statusbar"
|
|
59
|
+
statusbar_visible="draft,pending,approved"
|
|
60
|
+
/>
|
|
61
|
+
</header>
|
|
62
|
+
<div
|
|
63
|
+
class="alert alert-danger"
|
|
64
|
+
role="alert"
|
|
65
|
+
attrs="{'invisible': ['|',('is_supported', '=', True), ('template_uid', '=', False)]}"
|
|
66
|
+
>
|
|
67
|
+
This template is not supported because has <strong
|
|
68
|
+
>variables</strong> or <strong>buttons</strong>.
|
|
69
|
+
</div>
|
|
70
|
+
<sheet>
|
|
71
|
+
<div class="oe_title">
|
|
72
|
+
<label for="name" />
|
|
73
|
+
<h1>
|
|
74
|
+
<field
|
|
75
|
+
name="name"
|
|
76
|
+
placeholder="Name"
|
|
77
|
+
attrs="{'readonly': [('state', '!=', 'draft')]}"
|
|
78
|
+
/>
|
|
79
|
+
</h1>
|
|
80
|
+
</div>
|
|
81
|
+
<group>
|
|
82
|
+
<group>
|
|
83
|
+
<field
|
|
84
|
+
name="gateway_id"
|
|
85
|
+
attrs="{'readonly': [('state', '!=', 'draft')]}"
|
|
86
|
+
options="{'no_create': True}"
|
|
87
|
+
/>
|
|
88
|
+
<field
|
|
89
|
+
name="category"
|
|
90
|
+
attrs="{'readonly': [('state', '!=', 'draft')]}"
|
|
91
|
+
/>
|
|
92
|
+
<field
|
|
93
|
+
name="language"
|
|
94
|
+
attrs="{'readonly': [('state', '!=', 'draft')]}"
|
|
95
|
+
/>
|
|
96
|
+
</group>
|
|
97
|
+
<group>
|
|
98
|
+
<field
|
|
99
|
+
name="header"
|
|
100
|
+
attrs="{'readonly': [('state', '!=', 'draft')]}"
|
|
101
|
+
/>
|
|
102
|
+
<field
|
|
103
|
+
name="footer"
|
|
104
|
+
attrs="{'readonly': [('state', '!=', 'draft')]}"
|
|
105
|
+
/>
|
|
106
|
+
<field name="template_name" />
|
|
107
|
+
<field name="template_uid" />
|
|
108
|
+
<field
|
|
109
|
+
name="company_id"
|
|
110
|
+
options="{'no_create': True}"
|
|
111
|
+
groups="base.group_multi_company"
|
|
112
|
+
/>
|
|
113
|
+
<field name="is_supported" invisible="1" />
|
|
114
|
+
</group>
|
|
115
|
+
</group>
|
|
116
|
+
<notebook>
|
|
117
|
+
<page name="body" string="Body">
|
|
118
|
+
<field
|
|
119
|
+
name="body"
|
|
120
|
+
attrs="{'readonly': [('state', '!=', 'draft')]}"
|
|
121
|
+
/>
|
|
122
|
+
</page>
|
|
123
|
+
</notebook>
|
|
124
|
+
</sheet>
|
|
125
|
+
</form>
|
|
126
|
+
</field>
|
|
127
|
+
</record>
|
|
128
|
+
|
|
129
|
+
<record id="view_mail_whatsapp_template_search" model="ir.ui.view">
|
|
130
|
+
<field name="name">view.mail.whatsapp.template.search</field>
|
|
131
|
+
<field name="model">mail.whatsapp.template</field>
|
|
132
|
+
<field name="arch" type="xml">
|
|
133
|
+
<search>
|
|
134
|
+
<field name="name" />
|
|
135
|
+
<field name="gateway_id" />
|
|
136
|
+
<filter
|
|
137
|
+
name="group_by_state"
|
|
138
|
+
string="State"
|
|
139
|
+
context="{'group_by': 'state'}"
|
|
140
|
+
/>
|
|
141
|
+
</search>
|
|
142
|
+
</field>
|
|
143
|
+
</record>
|
|
144
|
+
|
|
145
|
+
<record id="action_mail_whatsapp_template_gateway" model="ir.actions.act_window">
|
|
146
|
+
<field name="name">WhatsApp Templates</field>
|
|
147
|
+
<field name="type">ir.actions.act_window</field>
|
|
148
|
+
<field name="res_model">mail.whatsapp.template</field>
|
|
149
|
+
<field name="view_mode">tree,form</field>
|
|
150
|
+
<field name="domain">[('gateway_id', '=', active_id)]</field>
|
|
151
|
+
<field name="context">{'default_gateway_id': active_id}</field>
|
|
152
|
+
</record>
|
|
153
|
+
|
|
154
|
+
</odoo>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Copyright 2024 Tecnativa - Carlos López
|
|
2
|
+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
|
3
|
+
|
|
4
|
+
import markupsafe
|
|
5
|
+
|
|
6
|
+
from odoo import api, fields, models
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MailComposeGatewayMessage(models.TransientModel):
|
|
10
|
+
_inherit = "mail.compose.gateway.message"
|
|
11
|
+
|
|
12
|
+
whatsapp_template_id = fields.Many2one(
|
|
13
|
+
"mail.whatsapp.template",
|
|
14
|
+
domain="""[
|
|
15
|
+
('state', '=', 'approved'),
|
|
16
|
+
('is_supported', '=', True)
|
|
17
|
+
]""",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
@api.onchange("whatsapp_template_id")
|
|
21
|
+
def onchange_whatsapp_template_id(self):
|
|
22
|
+
if self.whatsapp_template_id:
|
|
23
|
+
self.body = markupsafe.Markup(self.whatsapp_template_id.body)
|
|
24
|
+
|
|
25
|
+
def _action_send_mail(self, auto_commit=False):
|
|
26
|
+
if self.whatsapp_template_id:
|
|
27
|
+
self = self.with_context(whatsapp_template_id=self.whatsapp_template_id.id)
|
|
28
|
+
return super(MailComposeGatewayMessage, self)._action_send_mail(
|
|
29
|
+
auto_commit=auto_commit
|
|
30
|
+
)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8" ?>
|
|
2
|
+
<!-- Copyright 2024 Tecnativa - Carlos López
|
|
3
|
+
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
|
|
4
|
+
<odoo>
|
|
5
|
+
|
|
6
|
+
<record model="ir.ui.view" id="mail_compose_gateway_message_form_view">
|
|
7
|
+
<field name="model">mail.compose.gateway.message</field>
|
|
8
|
+
<field
|
|
9
|
+
name="inherit_id"
|
|
10
|
+
ref="mail_gateway.mail_compose_gateway_message_form_view"
|
|
11
|
+
/>
|
|
12
|
+
<field name="arch" type="xml">
|
|
13
|
+
<xpath expr="//field[@name='wizard_channel_ids']" position="after">
|
|
14
|
+
<field
|
|
15
|
+
name="whatsapp_template_id"
|
|
16
|
+
options="{'no_create': True, 'no_open': True}"
|
|
17
|
+
/>
|
|
18
|
+
</xpath>
|
|
19
|
+
<xpath expr="//field[@name='template_id']" position="attributes">
|
|
20
|
+
<attribute
|
|
21
|
+
name="attrs"
|
|
22
|
+
>{'invisible': [('whatsapp_template_id', '!=', False)]}</attribute>
|
|
23
|
+
</xpath>
|
|
24
|
+
<xpath expr="//field[@name='attachment_ids']" position="attributes">
|
|
25
|
+
<attribute
|
|
26
|
+
name="attrs"
|
|
27
|
+
>{'invisible': [('whatsapp_template_id', '!=', False)]}</attribute>
|
|
28
|
+
</xpath>
|
|
29
|
+
</field>
|
|
30
|
+
</record>
|
|
31
|
+
</odoo>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Copyright 2022 CreuBlanca
|
|
2
|
+
# Copyright 2024 Tecnativa - Carlos López
|
|
2
3
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
|
4
|
+
from datetime import datetime
|
|
3
5
|
|
|
4
6
|
from odoo import _, api, fields, models
|
|
5
7
|
from odoo.exceptions import UserError
|
|
@@ -17,7 +19,57 @@ class WhatsappComposer(models.TransientModel):
|
|
|
17
19
|
gateway_id = fields.Many2one(
|
|
18
20
|
"mail.gateway", domain=[("gateway_type", "=", "whatsapp")], required=True
|
|
19
21
|
)
|
|
22
|
+
template_id = fields.Many2one(
|
|
23
|
+
"mail.whatsapp.template",
|
|
24
|
+
domain="""[
|
|
25
|
+
('gateway_id', '=', gateway_id),
|
|
26
|
+
('state', '=', 'approved'),
|
|
27
|
+
('is_supported', '=', True)
|
|
28
|
+
]""",
|
|
29
|
+
)
|
|
20
30
|
body = fields.Text("Message")
|
|
31
|
+
is_required_template = fields.Boolean(compute="_compute_is_required_template")
|
|
32
|
+
|
|
33
|
+
@api.depends("res_model", "res_id", "number_field_name", "gateway_id")
|
|
34
|
+
def _compute_is_required_template(self):
|
|
35
|
+
MailMessage = self.env["mail.message"]
|
|
36
|
+
for wizard in self:
|
|
37
|
+
if (
|
|
38
|
+
not wizard.res_model
|
|
39
|
+
or not wizard.gateway_id
|
|
40
|
+
or not wizard.number_field_name
|
|
41
|
+
):
|
|
42
|
+
wizard.is_required_template = False
|
|
43
|
+
continue
|
|
44
|
+
record = self.env[wizard.res_model].browse(wizard.res_id)
|
|
45
|
+
is_required_template = True
|
|
46
|
+
channel = record._whatsapp_get_channel(
|
|
47
|
+
wizard.number_field_name, wizard.gateway_id
|
|
48
|
+
)
|
|
49
|
+
if channel:
|
|
50
|
+
last_message = MailMessage.search(
|
|
51
|
+
[
|
|
52
|
+
("gateway_type", "=", "whatsapp"),
|
|
53
|
+
("model", "=", channel._name),
|
|
54
|
+
("res_id", "=", channel.id),
|
|
55
|
+
],
|
|
56
|
+
order="date desc",
|
|
57
|
+
limit=1,
|
|
58
|
+
)
|
|
59
|
+
if last_message:
|
|
60
|
+
delta = (datetime.now() - last_message.date).total_seconds() / 3600
|
|
61
|
+
if delta < 24.0:
|
|
62
|
+
is_required_template = False
|
|
63
|
+
wizard.is_required_template = is_required_template
|
|
64
|
+
|
|
65
|
+
@api.onchange("gateway_id")
|
|
66
|
+
def onchange_gateway_id(self):
|
|
67
|
+
self.template_id = False
|
|
68
|
+
|
|
69
|
+
@api.onchange("template_id")
|
|
70
|
+
def onchange_template_id(self):
|
|
71
|
+
if self.template_id:
|
|
72
|
+
self.body = self.template_id.body
|
|
21
73
|
|
|
22
74
|
@api.model
|
|
23
75
|
def default_get(self, fields):
|
|
@@ -33,7 +85,7 @@ class WhatsappComposer(models.TransientModel):
|
|
|
33
85
|
if not record:
|
|
34
86
|
return
|
|
35
87
|
channel = record._whatsapp_get_channel(self.number_field_name, self.gateway_id)
|
|
36
|
-
channel.message_post(
|
|
88
|
+
channel.with_context(whatsapp_template_id=self.template_id.id).message_post(
|
|
37
89
|
body=self.body, subtype_xmlid="mail.mt_comment", message_type="comment"
|
|
38
90
|
)
|
|
39
91
|
|
|
@@ -13,11 +13,21 @@
|
|
|
13
13
|
name="gateway_id"
|
|
14
14
|
attrs="{'invisible': [('find_gateway', '=', False)]}"
|
|
15
15
|
/>
|
|
16
|
+
<field
|
|
17
|
+
name="template_id"
|
|
18
|
+
attrs="{'required': [('is_required_template', '=', True)]}"
|
|
19
|
+
options="{'no_create': True, 'no_open': True}"
|
|
20
|
+
/>
|
|
16
21
|
<field name="find_gateway" invisible="1" />
|
|
17
22
|
<field name="res_model" invisible="1" />
|
|
18
23
|
<field name="res_id" invisible="1" />
|
|
19
24
|
<field name="number_field_name" invisible="1" />
|
|
20
|
-
<field name="
|
|
25
|
+
<field name="is_required_template" invisible="1" />
|
|
26
|
+
<field
|
|
27
|
+
name="body"
|
|
28
|
+
attrs="{'readonly': [('template_id', '!=', False)]}"
|
|
29
|
+
force_save="1"
|
|
30
|
+
/>
|
|
21
31
|
</group>
|
|
22
32
|
<footer>
|
|
23
33
|
<button
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: odoo-addon-mail_gateway_whatsapp
|
|
3
|
-
Version: 16.0.1.
|
|
3
|
+
Version: 16.0.1.1.0
|
|
4
4
|
Summary: Set a gateway for whatsapp
|
|
5
5
|
Home-page: https://github.com/OCA/social
|
|
6
6
|
Author: Creu Blanca, Dixmit, Odoo Community Association (OCA)
|
|
@@ -24,7 +24,7 @@ Mail Whatsapp Gateway
|
|
|
24
24
|
!! This file is generated by oca-gen-addon-readme !!
|
|
25
25
|
!! changes will be overwritten. !!
|
|
26
26
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
27
|
-
!! source digest: sha256:
|
|
27
|
+
!! source digest: sha256:70475641a2c7498a1ee67811eadb397e33c33435cdd2efb8edce729026ad09c4
|
|
28
28
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
29
29
|
|
|
30
30
|
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
|
|
@@ -79,6 +79,7 @@ In order to make it you must follow this steps:
|
|
|
79
79
|
|
|
80
80
|
* Use the Meta App authentication key as `Token` field
|
|
81
81
|
* Use the Meta App Phone Number ID as `Whatsapp from Phone` field
|
|
82
|
+
* Use the Meta Account Business ID as `Whatsapp account` field (only if you need sync templates)
|
|
82
83
|
* Write your own `Webhook key`
|
|
83
84
|
* Use the Application Secret Key on `Whatsapp Security Key`. It will be used in order to validate the data
|
|
84
85
|
* Press the `Integrate Webhook Key`. In this case, it will not integrate it, we need to make it manually
|
|
@@ -96,6 +97,14 @@ Usage
|
|
|
96
97
|
2. Wait until someone starts a conversation.
|
|
97
98
|
3. Now you will be able to respond and receive messages to this person.
|
|
98
99
|
|
|
100
|
+
Known issues / Roadmap
|
|
101
|
+
======================
|
|
102
|
+
|
|
103
|
+
**WhatsApp templates**
|
|
104
|
+
|
|
105
|
+
* Add support for `Variables`
|
|
106
|
+
* Add support for `Buttons`
|
|
107
|
+
|
|
99
108
|
Bug Tracker
|
|
100
109
|
===========
|
|
101
110
|
|
|
@@ -120,6 +129,9 @@ Contributors
|
|
|
120
129
|
|
|
121
130
|
* Olga Marco <olga.marco@creublanca.es>
|
|
122
131
|
* Enric Tobella <etobella@creublanca.es>
|
|
132
|
+
* `Tecnativa <https://www.tecnativa.com>`_:
|
|
133
|
+
|
|
134
|
+
* Carlos Lopez
|
|
123
135
|
|
|
124
136
|
Other credits
|
|
125
137
|
~~~~~~~~~~~~~
|