odoo-addon-mail-gateway-whatsapp 17.0.1.1.1__py3-none-any.whl → 18.0.1.0.1__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 +6 -6
- odoo/addons/mail_gateway_whatsapp/__manifest__.py +1 -1
- odoo/addons/mail_gateway_whatsapp/i18n/es.po +12 -273
- odoo/addons/mail_gateway_whatsapp/i18n/it.po +12 -273
- odoo/addons/mail_gateway_whatsapp/i18n/mail_gateway_whatsapp.pot +1 -302
- odoo/addons/mail_gateway_whatsapp/models/mail_gateway.py +1 -1
- odoo/addons/mail_gateway_whatsapp/models/mail_gateway_whatsapp.py +14 -13
- odoo/addons/mail_gateway_whatsapp/models/mail_whatsapp_template.py +20 -544
- odoo/addons/mail_gateway_whatsapp/security/ir.model.access.csv +0 -4
- odoo/addons/mail_gateway_whatsapp/static/description/index.html +4 -4
- odoo/addons/mail_gateway_whatsapp/static/src/components/message/message_patch.esm.js +0 -1
- odoo/addons/mail_gateway_whatsapp/static/src/components/phone_field/phone_field.esm.js +2 -3
- odoo/addons/mail_gateway_whatsapp/static/src/components/send_whatsapp_button/send_whatsapp_button.esm.js +3 -5
- odoo/addons/mail_gateway_whatsapp/static/src/components/send_whatsapp_button/send_whatsapp_button.xml +5 -2
- odoo/addons/mail_gateway_whatsapp/tests/test_mail_gateway_whatsapp.py +22 -182
- odoo/addons/mail_gateway_whatsapp/tests/test_mail_whatsapp_template.py +2 -72
- odoo/addons/mail_gateway_whatsapp/tools/const.py +0 -2
- odoo/addons/mail_gateway_whatsapp/views/mail_gateway.xml +23 -24
- odoo/addons/mail_gateway_whatsapp/views/mail_whatsapp_template_views.xml +4 -60
- odoo/addons/mail_gateway_whatsapp/wizards/mail_compose_gateway_message.py +3 -9
- odoo/addons/mail_gateway_whatsapp/wizards/mail_compose_gateway_message.xml +0 -1
- odoo/addons/mail_gateway_whatsapp/wizards/whatsapp_composer.py +2 -3
- odoo/addons/mail_gateway_whatsapp/wizards/whatsapp_composer.xml +1 -4
- {odoo_addon_mail_gateway_whatsapp-17.0.1.1.1.dist-info → odoo_addon_mail_gateway_whatsapp-18.0.1.0.1.dist-info}/METADATA +10 -10
- {odoo_addon_mail_gateway_whatsapp-17.0.1.1.1.dist-info → odoo_addon_mail_gateway_whatsapp-18.0.1.0.1.dist-info}/RECORD +27 -27
- {odoo_addon_mail_gateway_whatsapp-17.0.1.1.1.dist-info → odoo_addon_mail_gateway_whatsapp-18.0.1.0.1.dist-info}/WHEEL +0 -0
- {odoo_addon_mail_gateway_whatsapp-17.0.1.1.1.dist-info → odoo_addon_mail_gateway_whatsapp-18.0.1.0.1.dist-info}/top_level.txt +0 -0
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
# Copyright 2024 Tecnativa - Carlos López
|
|
2
2
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
|
3
3
|
import re
|
|
4
|
-
from urllib.parse import urlparse
|
|
5
4
|
|
|
6
5
|
import requests
|
|
7
6
|
from werkzeug.urls import url_join
|
|
8
7
|
|
|
9
|
-
from odoo import
|
|
10
|
-
from odoo.exceptions import UserError
|
|
8
|
+
from odoo import api, fields, models
|
|
9
|
+
from odoo.exceptions import UserError
|
|
11
10
|
from odoo.tools import ustr
|
|
12
11
|
|
|
13
|
-
from
|
|
14
|
-
from odoo.addons.phone_validation.tools import phone_validation
|
|
15
|
-
|
|
16
|
-
from ..tools.const import REG_VARIABLE, supported_languages
|
|
12
|
+
from ..tools.const import supported_languages
|
|
17
13
|
from .mail_gateway import BASE_URL
|
|
18
14
|
|
|
19
15
|
|
|
@@ -65,26 +61,6 @@ class MailWhatsAppTemplate(models.Model):
|
|
|
65
61
|
company_id = fields.Many2one(
|
|
66
62
|
"res.company", related="gateway_id.company_id", store=True
|
|
67
63
|
)
|
|
68
|
-
model_id = fields.Many2one(
|
|
69
|
-
string="Applies to",
|
|
70
|
-
comodel_name="ir.model",
|
|
71
|
-
default=lambda self: self.env["ir.model"]._get_id("res.partner"),
|
|
72
|
-
required=True,
|
|
73
|
-
ondelete="cascade",
|
|
74
|
-
)
|
|
75
|
-
model = fields.Char(string="Related model", related="model_id.model", store=True)
|
|
76
|
-
variable_ids = fields.One2many(
|
|
77
|
-
"mail.whatsapp.template.variable",
|
|
78
|
-
"template_id",
|
|
79
|
-
string="Variables",
|
|
80
|
-
store=True,
|
|
81
|
-
compute="_compute_variable_ids",
|
|
82
|
-
precompute=True,
|
|
83
|
-
readonly=False,
|
|
84
|
-
)
|
|
85
|
-
button_ids = fields.One2many(
|
|
86
|
-
"mail.whatsapp.template.button", "template_id", string="Buttons"
|
|
87
|
-
)
|
|
88
64
|
|
|
89
65
|
_sql_constraints = [
|
|
90
66
|
(
|
|
@@ -94,67 +70,6 @@ class MailWhatsAppTemplate(models.Model):
|
|
|
94
70
|
)
|
|
95
71
|
]
|
|
96
72
|
|
|
97
|
-
@api.constrains("button_ids")
|
|
98
|
-
def _check_buttons(self):
|
|
99
|
-
for template in self:
|
|
100
|
-
if len(template.button_ids) > 10:
|
|
101
|
-
raise ValidationError(_("A maximum of 10 buttons is allowed."))
|
|
102
|
-
url_buttons = template.button_ids.filtered(
|
|
103
|
-
lambda button: button.button_type == "url"
|
|
104
|
-
)
|
|
105
|
-
phone_number_buttons = template.button_ids.filtered(
|
|
106
|
-
lambda button: button.button_type == "phone_number"
|
|
107
|
-
)
|
|
108
|
-
if len(url_buttons) > 2:
|
|
109
|
-
raise ValidationError(_("A maximum of 2 URL buttons is allowed."))
|
|
110
|
-
if len(phone_number_buttons) > 1:
|
|
111
|
-
raise ValidationError(
|
|
112
|
-
_("A maximum of 1 Call Number button is allowed.")
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
@api.constrains("variable_ids")
|
|
116
|
-
def _check_variables(self):
|
|
117
|
-
for template in self:
|
|
118
|
-
body_variables = template.variable_ids.filtered(
|
|
119
|
-
lambda var: var.line_type == "body"
|
|
120
|
-
)
|
|
121
|
-
header_variables = template.variable_ids.filtered(
|
|
122
|
-
lambda var: var.line_type == "header"
|
|
123
|
-
)
|
|
124
|
-
if len(header_variables) > 1:
|
|
125
|
-
raise ValidationError(
|
|
126
|
-
_("There should be exactly 1 variable in the header.")
|
|
127
|
-
)
|
|
128
|
-
if header_variables and header_variables._extract_variable_index() != 1:
|
|
129
|
-
raise ValidationError(
|
|
130
|
-
_("Variable in the header should be used as {{1}}")
|
|
131
|
-
)
|
|
132
|
-
variable_indices = sorted(
|
|
133
|
-
var._extract_variable_index() for var in body_variables
|
|
134
|
-
)
|
|
135
|
-
if len(variable_indices) > 0 and (
|
|
136
|
-
variable_indices[0] != 1 or variable_indices[-1] != len(body_variables)
|
|
137
|
-
):
|
|
138
|
-
missing = (
|
|
139
|
-
next(
|
|
140
|
-
(
|
|
141
|
-
index
|
|
142
|
-
for index in range(1, len(body_variables))
|
|
143
|
-
if variable_indices[index - 1] + 1
|
|
144
|
-
!= variable_indices[index]
|
|
145
|
-
),
|
|
146
|
-
0,
|
|
147
|
-
)
|
|
148
|
-
+ 1
|
|
149
|
-
)
|
|
150
|
-
raise ValidationError(
|
|
151
|
-
_(
|
|
152
|
-
"Body variables should start at 1 and not skip any number, "
|
|
153
|
-
"missing %d",
|
|
154
|
-
missing,
|
|
155
|
-
)
|
|
156
|
-
)
|
|
157
|
-
|
|
158
73
|
@api.depends("name", "state", "template_uid")
|
|
159
74
|
def _compute_template_name(self):
|
|
160
75
|
for template in self:
|
|
@@ -162,57 +77,9 @@ class MailWhatsAppTemplate(models.Model):
|
|
|
162
77
|
template.state == "draft" and not template.template_uid
|
|
163
78
|
):
|
|
164
79
|
template.template_name = re.sub(
|
|
165
|
-
r"\W+", "_",
|
|
80
|
+
r"\W+", "_", self.env["ir.http"]._slugify(template.name or "")
|
|
166
81
|
)
|
|
167
82
|
|
|
168
|
-
@api.depends("header", "body")
|
|
169
|
-
def _compute_variable_ids(self):
|
|
170
|
-
for template in self:
|
|
171
|
-
to_remove = self.env["mail.whatsapp.template.variable"]
|
|
172
|
-
to_keep = self.env["mail.whatsapp.template.variable"]
|
|
173
|
-
new_values = []
|
|
174
|
-
header_variables = list(re.findall(REG_VARIABLE, template.header or ""))
|
|
175
|
-
body_variables = set(re.findall(REG_VARIABLE, template.body or ""))
|
|
176
|
-
# header
|
|
177
|
-
current_header_variable = template.variable_ids.filtered(
|
|
178
|
-
lambda line: line.line_type == "header"
|
|
179
|
-
)
|
|
180
|
-
if header_variables and not current_header_variable:
|
|
181
|
-
new_values.append(
|
|
182
|
-
{
|
|
183
|
-
"name": header_variables[0],
|
|
184
|
-
"line_type": "header",
|
|
185
|
-
"template_id": template.id,
|
|
186
|
-
}
|
|
187
|
-
)
|
|
188
|
-
elif not header_variables and current_header_variable:
|
|
189
|
-
to_remove += current_header_variable
|
|
190
|
-
elif current_header_variable:
|
|
191
|
-
to_keep += current_header_variable
|
|
192
|
-
# body
|
|
193
|
-
current_body_variables = template.variable_ids.filtered(
|
|
194
|
-
lambda line: line.line_type == "body"
|
|
195
|
-
)
|
|
196
|
-
new_body_variable_names = [
|
|
197
|
-
var_name
|
|
198
|
-
for var_name in body_variables
|
|
199
|
-
if var_name not in current_body_variables.mapped("name")
|
|
200
|
-
]
|
|
201
|
-
deleted_variables = current_body_variables.filtered(
|
|
202
|
-
lambda var, body_variables=body_variables: var.name
|
|
203
|
-
not in body_variables
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
new_values += [
|
|
207
|
-
{"name": var_name, "line_type": "body", "template_id": template.id}
|
|
208
|
-
for var_name in set(new_body_variable_names)
|
|
209
|
-
]
|
|
210
|
-
to_remove += deleted_variables
|
|
211
|
-
to_keep += current_body_variables - deleted_variables
|
|
212
|
-
template.variable_ids = [(3, to_remove.id) for to_remove in to_remove] + [
|
|
213
|
-
Command.create(vals) for vals in new_values
|
|
214
|
-
]
|
|
215
|
-
|
|
216
83
|
def button_back2draft(self):
|
|
217
84
|
self.write({"state": "draft"})
|
|
218
85
|
|
|
@@ -227,7 +94,7 @@ class MailWhatsAppTemplate(models.Model):
|
|
|
227
94
|
payload = self._prepare_values_to_export()
|
|
228
95
|
response = requests.post(
|
|
229
96
|
template_url,
|
|
230
|
-
headers={"Authorization": "Bearer
|
|
97
|
+
headers={"Authorization": f"Bearer {gateway.token}"},
|
|
231
98
|
json=payload,
|
|
232
99
|
timeout=10,
|
|
233
100
|
)
|
|
@@ -256,39 +123,23 @@ class MailWhatsAppTemplate(models.Model):
|
|
|
256
123
|
}
|
|
257
124
|
|
|
258
125
|
def _prepare_components_to_export(self):
|
|
259
|
-
|
|
260
|
-
body_params = self.variable_ids.filtered(lambda line: line.line_type == "body")
|
|
261
|
-
if body_params:
|
|
262
|
-
body_component["example"] = {
|
|
263
|
-
"body_text": [body_params.mapped("sample_value")]
|
|
264
|
-
}
|
|
265
|
-
components = [body_component]
|
|
126
|
+
components = [{"type": "BODY", "text": self.body}]
|
|
266
127
|
if self.header:
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
header_component["example"] = {
|
|
273
|
-
"header_text": header_params.mapped("sample_value")
|
|
128
|
+
components.append(
|
|
129
|
+
{
|
|
130
|
+
"type": "HEADER",
|
|
131
|
+
"format": "text",
|
|
132
|
+
"text": self.header,
|
|
274
133
|
}
|
|
275
|
-
|
|
134
|
+
)
|
|
276
135
|
if self.footer:
|
|
277
|
-
components.append(
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
button_data["url"] += "{{1}}"
|
|
285
|
-
button_data["example"] = button.variable_ids[0].sample_value
|
|
286
|
-
elif button.button_type == "phone_number":
|
|
287
|
-
button_data["phone_number"] = button.call_number
|
|
288
|
-
buttons.append(button_data)
|
|
289
|
-
if buttons:
|
|
290
|
-
components.append({"type": "BUTTONS", "buttons": buttons})
|
|
291
|
-
# TODO: add more components(location, etc)
|
|
136
|
+
components.append(
|
|
137
|
+
{
|
|
138
|
+
"type": "FOOTER",
|
|
139
|
+
"text": self.footer,
|
|
140
|
+
}
|
|
141
|
+
)
|
|
142
|
+
# TODO: add more components(buttons, location, etc)
|
|
292
143
|
return components
|
|
293
144
|
|
|
294
145
|
def button_sync_template(self):
|
|
@@ -301,7 +152,7 @@ class MailWhatsAppTemplate(models.Model):
|
|
|
301
152
|
try:
|
|
302
153
|
response = requests.get(
|
|
303
154
|
template_url,
|
|
304
|
-
headers={"Authorization": "Bearer
|
|
155
|
+
headers={"Authorization": f"Bearer {gateway.token}"},
|
|
305
156
|
timeout=10,
|
|
306
157
|
)
|
|
307
158
|
response.raise_for_status()
|
|
@@ -334,382 +185,7 @@ class MailWhatsAppTemplate(models.Model):
|
|
|
334
185
|
vals["body"] = component["text"]
|
|
335
186
|
elif component["type"] == "FOOTER":
|
|
336
187
|
vals["footer"] = component["text"]
|
|
337
|
-
elif component["type"] == "BUTTONS":
|
|
338
|
-
for index, button in enumerate(component["buttons"]):
|
|
339
|
-
if button["type"] in ("URL", "PHONE_NUMBER", "QUICK_REPLY"):
|
|
340
|
-
button_vals = {
|
|
341
|
-
"sequence": index,
|
|
342
|
-
"name": button["text"],
|
|
343
|
-
"button_type": button["type"].lower(),
|
|
344
|
-
"call_number": button.get("phone_number"),
|
|
345
|
-
"website_url": button.get("url"),
|
|
346
|
-
}
|
|
347
|
-
vals.setdefault("button_ids", [])
|
|
348
|
-
button = self.button_ids.filtered(
|
|
349
|
-
lambda btn, button=button: btn.name == button["text"]
|
|
350
|
-
)
|
|
351
|
-
if button:
|
|
352
|
-
vals["button_ids"].append(
|
|
353
|
-
Command.update(button.id, button_vals)
|
|
354
|
-
)
|
|
355
|
-
else:
|
|
356
|
-
vals["button_ids"].append(Command.create(button_vals))
|
|
357
188
|
else:
|
|
358
189
|
is_supported = False
|
|
359
190
|
vals["is_supported"] = is_supported
|
|
360
191
|
return vals
|
|
361
|
-
|
|
362
|
-
def _prepare_header_component(self, variable_ids_value):
|
|
363
|
-
header = []
|
|
364
|
-
if self.header and variable_ids_value.get("header-{{1}}"):
|
|
365
|
-
value = variable_ids_value.get("header-{{1}}") or (self.header or {}) or ""
|
|
366
|
-
header = {"type": "header", "parameters": [{"type": "text", "text": value}]}
|
|
367
|
-
return header
|
|
368
|
-
|
|
369
|
-
def _prepare_body_components(self, variable_ids_value):
|
|
370
|
-
if not self.variable_ids:
|
|
371
|
-
return None
|
|
372
|
-
parameters = []
|
|
373
|
-
for body_val in self.variable_ids.filtered(
|
|
374
|
-
lambda line: line.line_type == "body"
|
|
375
|
-
):
|
|
376
|
-
parameters.append(
|
|
377
|
-
{
|
|
378
|
-
"type": "text",
|
|
379
|
-
"text": variable_ids_value.get(
|
|
380
|
-
f"{body_val.line_type}-{body_val.name}"
|
|
381
|
-
)
|
|
382
|
-
or " ",
|
|
383
|
-
}
|
|
384
|
-
)
|
|
385
|
-
return {"type": "body", "parameters": parameters}
|
|
386
|
-
|
|
387
|
-
def _prepare_button_components(self, variable_ids_value):
|
|
388
|
-
components = []
|
|
389
|
-
dynamic_buttons = self.button_ids.filtered(
|
|
390
|
-
lambda line: line.url_type == "dynamic"
|
|
391
|
-
)
|
|
392
|
-
index = {button: i for i, button in enumerate(self.button_ids)}
|
|
393
|
-
for button in dynamic_buttons:
|
|
394
|
-
dynamic_url = button.website_url
|
|
395
|
-
value = variable_ids_value.get(f"button-{button.name}") or " "
|
|
396
|
-
value = value.replace(dynamic_url, "").lstrip("/")
|
|
397
|
-
components.append(
|
|
398
|
-
{
|
|
399
|
-
"type": "button",
|
|
400
|
-
"sub_type": "url",
|
|
401
|
-
"index": index.get(button),
|
|
402
|
-
"parameters": [{"type": "text", "text": value}],
|
|
403
|
-
}
|
|
404
|
-
)
|
|
405
|
-
return components
|
|
406
|
-
|
|
407
|
-
def prepare_value_to_send(self):
|
|
408
|
-
self.ensure_one()
|
|
409
|
-
model_name = self.model_id.model
|
|
410
|
-
rec_id = self.env.context.get("default_res_id")
|
|
411
|
-
if rec_id is None:
|
|
412
|
-
rec_ids = self.env.context.get("res_id")
|
|
413
|
-
if rec_ids:
|
|
414
|
-
rec_id = rec_ids
|
|
415
|
-
else:
|
|
416
|
-
rec_id = None
|
|
417
|
-
if model_name and rec_id:
|
|
418
|
-
record = self.env[model_name].browse(int(rec_id))
|
|
419
|
-
components = []
|
|
420
|
-
variable_ids_value = self.variable_ids._get_variables_value(record)
|
|
421
|
-
# generate components
|
|
422
|
-
header = self._prepare_header_component(variable_ids_value=variable_ids_value)
|
|
423
|
-
body = self._prepare_body_components(variable_ids_value=variable_ids_value)
|
|
424
|
-
buttons = self._prepare_button_components(variable_ids_value=variable_ids_value)
|
|
425
|
-
if header:
|
|
426
|
-
components.append(header)
|
|
427
|
-
if body:
|
|
428
|
-
components.append(body)
|
|
429
|
-
components.extend(buttons)
|
|
430
|
-
return components
|
|
431
|
-
|
|
432
|
-
def render_body_message(self):
|
|
433
|
-
self.ensure_one()
|
|
434
|
-
model_name = self.model_id.model
|
|
435
|
-
rec_id = self.env.context.get("default_res_id")
|
|
436
|
-
if rec_id is None:
|
|
437
|
-
rec_ids = self.env.context.get("default_res_ids")
|
|
438
|
-
if isinstance(rec_ids, list) and rec_ids:
|
|
439
|
-
rec_id = rec_ids[0]
|
|
440
|
-
else:
|
|
441
|
-
rec_id = None
|
|
442
|
-
if model_name and rec_id:
|
|
443
|
-
record = self.env[model_name].browse(int(rec_id))
|
|
444
|
-
header = ""
|
|
445
|
-
if self.header:
|
|
446
|
-
header = self.header
|
|
447
|
-
header_vars = self.variable_ids.filtered(lambda v: v.line_type == "header")
|
|
448
|
-
for i, var in enumerate(header_vars, start=1):
|
|
449
|
-
placeholder = f"{{{{{i}}}}}"
|
|
450
|
-
value = var._get_variables_value(record).get(
|
|
451
|
-
f"header-{placeholder}", ""
|
|
452
|
-
)
|
|
453
|
-
header = header.replace(placeholder, str(value))
|
|
454
|
-
body = self.body or ""
|
|
455
|
-
body_vars = self.variable_ids.filtered(lambda v: v.line_type == "body")
|
|
456
|
-
for i, var in enumerate(body_vars, start=1):
|
|
457
|
-
placeholder = f"{{{{{i}}}}}"
|
|
458
|
-
value = var._get_variables_value(record).get(f"body-{placeholder}", "")
|
|
459
|
-
body = body.replace(placeholder, str(value))
|
|
460
|
-
message = f"*{header}*\n\n{body}" if header else body
|
|
461
|
-
return message
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
class MailWhatsAppTemplateVariable(models.Model):
|
|
465
|
-
_name = "mail.whatsapp.template.variable"
|
|
466
|
-
_description = "WhatsApp Template Variable"
|
|
467
|
-
_order = "line_type desc, name, id"
|
|
468
|
-
|
|
469
|
-
name = fields.Char(string="Placeholder", required=True)
|
|
470
|
-
template_id = fields.Many2one(
|
|
471
|
-
comodel_name="mail.whatsapp.template", required=True, ondelete="cascade"
|
|
472
|
-
)
|
|
473
|
-
model = fields.Char(string="Model Name", related="template_id.model")
|
|
474
|
-
line_type = fields.Selection(
|
|
475
|
-
[("header", "Header"), ("body", "Body"), ("button", "Button")], required=True
|
|
476
|
-
)
|
|
477
|
-
field_name = fields.Char(string="Field")
|
|
478
|
-
sample_value = fields.Char(default="Sample Value", required=True)
|
|
479
|
-
button_id = fields.Many2one("mail.whatsapp.template.button", ondelete="cascade")
|
|
480
|
-
|
|
481
|
-
_sql_constraints = [
|
|
482
|
-
(
|
|
483
|
-
"name_type_template_unique",
|
|
484
|
-
"UNIQUE(name, line_type, template_id,button_id)",
|
|
485
|
-
"Variable names must be unique by template",
|
|
486
|
-
),
|
|
487
|
-
]
|
|
488
|
-
|
|
489
|
-
@api.constrains("field_name")
|
|
490
|
-
def _check_field_name(self):
|
|
491
|
-
failing = self.browse()
|
|
492
|
-
missing = self.filtered(lambda variable: not variable.field_name)
|
|
493
|
-
if missing:
|
|
494
|
-
raise ValidationError(
|
|
495
|
-
_(
|
|
496
|
-
"Field template variables %(variables)s "
|
|
497
|
-
"must be associated with a field.",
|
|
498
|
-
variables=", ".join(missing.mapped("name")),
|
|
499
|
-
)
|
|
500
|
-
)
|
|
501
|
-
for variable in self:
|
|
502
|
-
model = self.env[variable.model]
|
|
503
|
-
if not model.check_access_rights("read", raise_exception=False):
|
|
504
|
-
model_description = (
|
|
505
|
-
self.env["ir.model"]._get(variable.model).display_name
|
|
506
|
-
)
|
|
507
|
-
raise ValidationError(
|
|
508
|
-
_("You can not select field of %(model)s.", model=model_description)
|
|
509
|
-
)
|
|
510
|
-
try:
|
|
511
|
-
variable._extract_value_from_field_path(model)
|
|
512
|
-
except UserError:
|
|
513
|
-
failing += variable
|
|
514
|
-
if failing:
|
|
515
|
-
model_description = (
|
|
516
|
-
self.env["ir.model"]._get(failing.mapped("model")[0]).display_name
|
|
517
|
-
)
|
|
518
|
-
raise ValidationError(
|
|
519
|
-
_(
|
|
520
|
-
"Variables %(field_names)s do not seem to be valid field path "
|
|
521
|
-
"for model %(model_name)s.",
|
|
522
|
-
field_names=", ".join(failing.mapped("field_name")),
|
|
523
|
-
model_name=model_description,
|
|
524
|
-
)
|
|
525
|
-
)
|
|
526
|
-
|
|
527
|
-
@api.constrains("name")
|
|
528
|
-
def _check_name(self):
|
|
529
|
-
for variable in self:
|
|
530
|
-
if not variable._extract_variable_index():
|
|
531
|
-
raise ValidationError(
|
|
532
|
-
_(
|
|
533
|
-
"Template variable should be in format {{number}}. "
|
|
534
|
-
"Cannot parse '%(placeholder)s'",
|
|
535
|
-
placeholder=variable.name,
|
|
536
|
-
)
|
|
537
|
-
)
|
|
538
|
-
|
|
539
|
-
@api.depends("line_type", "name")
|
|
540
|
-
def _compute_display_name(self):
|
|
541
|
-
type_names = dict(self._fields["line_type"]._description_selection(self.env))
|
|
542
|
-
for variable in self:
|
|
543
|
-
type_name = type_names[variable.line_type or "body"]
|
|
544
|
-
variable.display_name = (
|
|
545
|
-
type_name
|
|
546
|
-
if variable.line_type == "header"
|
|
547
|
-
else f"{type_name} - {variable.name}"
|
|
548
|
-
)
|
|
549
|
-
|
|
550
|
-
@api.onchange("model")
|
|
551
|
-
def _onchange_model_id(self):
|
|
552
|
-
self.field_name = False
|
|
553
|
-
|
|
554
|
-
def _get_variables_value(self, record):
|
|
555
|
-
value_by_name = {}
|
|
556
|
-
for variable in self:
|
|
557
|
-
value = variable._extract_value_from_field_path(record)
|
|
558
|
-
value_str = value and str(value) or ""
|
|
559
|
-
value_key = f"{variable.line_type}-{variable.name}"
|
|
560
|
-
value_by_name[value_key] = value_str
|
|
561
|
-
return value_by_name
|
|
562
|
-
|
|
563
|
-
def _extract_variable_index(self):
|
|
564
|
-
"""Extract variable index, located between '{{}}' markers."""
|
|
565
|
-
self.ensure_one()
|
|
566
|
-
try:
|
|
567
|
-
return int(self.name.replace("{{", "").replace("}}", ""))
|
|
568
|
-
except ValueError:
|
|
569
|
-
return None
|
|
570
|
-
|
|
571
|
-
def _extract_value_from_field_path(self, record):
|
|
572
|
-
field_path = self.field_name
|
|
573
|
-
if not field_path:
|
|
574
|
-
return ""
|
|
575
|
-
try:
|
|
576
|
-
field_value = record.mapped(field_path)
|
|
577
|
-
except Exception as err:
|
|
578
|
-
raise UserError(
|
|
579
|
-
_(
|
|
580
|
-
"We were not able to fetch value of field: %(field)s",
|
|
581
|
-
field=field_path,
|
|
582
|
-
)
|
|
583
|
-
) from err
|
|
584
|
-
if isinstance(field_value, models.Model):
|
|
585
|
-
return " ".join((value.display_name or "") for value in field_value)
|
|
586
|
-
# find last field / last model when having chained fields
|
|
587
|
-
# e.g. 'partner_id.country_id.state' -> ['partner_id.country_id', 'state']
|
|
588
|
-
field_path_models = field_path.rsplit(".", 1)
|
|
589
|
-
if len(field_path_models) > 1:
|
|
590
|
-
last_model_path, last_fname = field_path_models
|
|
591
|
-
last_model = record.mapped(last_model_path)
|
|
592
|
-
else:
|
|
593
|
-
last_model, last_fname = record, field_path
|
|
594
|
-
last_field = last_model._fields[last_fname]
|
|
595
|
-
# return value instead of the key
|
|
596
|
-
if last_field.type == "selection":
|
|
597
|
-
return " ".join(
|
|
598
|
-
last_field.convert_to_export(value, last_model) for value in field_value
|
|
599
|
-
)
|
|
600
|
-
return " ".join(
|
|
601
|
-
str(value if value is not False and value is not None else "")
|
|
602
|
-
for value in field_value
|
|
603
|
-
)
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
class MailWhatsAppTemplateButton(models.Model):
|
|
607
|
-
_name = "mail.whatsapp.template.button"
|
|
608
|
-
_description = "WhatsApp Template Button"
|
|
609
|
-
_order = "sequence,id"
|
|
610
|
-
|
|
611
|
-
sequence = fields.Integer()
|
|
612
|
-
name = fields.Char(string="Button Text", size=25)
|
|
613
|
-
template_id = fields.Many2one(
|
|
614
|
-
comodel_name="mail.whatsapp.template", required=True, ondelete="cascade"
|
|
615
|
-
)
|
|
616
|
-
button_type = fields.Selection(
|
|
617
|
-
[
|
|
618
|
-
("quick_reply", "Quick Reply"),
|
|
619
|
-
("url", "Visit Website"),
|
|
620
|
-
("phone_number", "Call Number"),
|
|
621
|
-
],
|
|
622
|
-
string="Type",
|
|
623
|
-
required=True,
|
|
624
|
-
default="quick_reply",
|
|
625
|
-
)
|
|
626
|
-
url_type = fields.Selection(
|
|
627
|
-
[("static", "Static"), ("dynamic", "Dynamic")],
|
|
628
|
-
default="static",
|
|
629
|
-
)
|
|
630
|
-
website_url = fields.Char()
|
|
631
|
-
call_number = fields.Char()
|
|
632
|
-
variable_ids = fields.One2many(
|
|
633
|
-
"mail.whatsapp.template.variable",
|
|
634
|
-
"button_id",
|
|
635
|
-
compute="_compute_variable_ids",
|
|
636
|
-
precompute=True,
|
|
637
|
-
store=True,
|
|
638
|
-
copy=True,
|
|
639
|
-
)
|
|
640
|
-
|
|
641
|
-
_sql_constraints = [
|
|
642
|
-
(
|
|
643
|
-
"unique_name_per_template",
|
|
644
|
-
"UNIQUE(name, template_id)",
|
|
645
|
-
"Button name must be unique by template",
|
|
646
|
-
)
|
|
647
|
-
]
|
|
648
|
-
|
|
649
|
-
@api.constrains("button_type", "url_type", "website_url")
|
|
650
|
-
def _validate_website_url(self):
|
|
651
|
-
for button in self.filtered(lambda button: button.button_type == "url"):
|
|
652
|
-
parsed_url = urlparse(button.website_url)
|
|
653
|
-
if not (parsed_url.scheme in {"http", "https"} and parsed_url.netloc):
|
|
654
|
-
raise ValidationError(
|
|
655
|
-
_(
|
|
656
|
-
"Please enter a valid URL in the format 'https://www.example.com'."
|
|
657
|
-
)
|
|
658
|
-
)
|
|
659
|
-
|
|
660
|
-
@api.constrains("call_number")
|
|
661
|
-
def _validate_call_number(self):
|
|
662
|
-
for button in self:
|
|
663
|
-
if button.button_type == "phone_number":
|
|
664
|
-
phone_validation.phone_format(button.call_number, False, False)
|
|
665
|
-
|
|
666
|
-
@api.depends("button_type", "url_type", "website_url", "name")
|
|
667
|
-
def _compute_variable_ids(self):
|
|
668
|
-
dynamic_urls = self.filtered(
|
|
669
|
-
lambda button: button.button_type == "url" and button.url_type == "dynamic"
|
|
670
|
-
)
|
|
671
|
-
to_clear = self - dynamic_urls
|
|
672
|
-
for button in dynamic_urls:
|
|
673
|
-
if button.variable_ids:
|
|
674
|
-
button.variable_ids = [
|
|
675
|
-
(
|
|
676
|
-
1,
|
|
677
|
-
button.variable_ids[0].id,
|
|
678
|
-
{
|
|
679
|
-
"sample_value": button.website_url,
|
|
680
|
-
"line_type": "button",
|
|
681
|
-
"name": button.name,
|
|
682
|
-
"button_id": button.id,
|
|
683
|
-
"template_id": button.template_id.id,
|
|
684
|
-
},
|
|
685
|
-
),
|
|
686
|
-
]
|
|
687
|
-
else:
|
|
688
|
-
button.variable_ids = [
|
|
689
|
-
(
|
|
690
|
-
0,
|
|
691
|
-
0,
|
|
692
|
-
{
|
|
693
|
-
"sample_value": button.website_url,
|
|
694
|
-
"line_type": "button",
|
|
695
|
-
"name": button.name,
|
|
696
|
-
"button_id": button.id,
|
|
697
|
-
"template_id": button.template_id.id,
|
|
698
|
-
},
|
|
699
|
-
),
|
|
700
|
-
]
|
|
701
|
-
if to_clear:
|
|
702
|
-
to_clear.variable_ids = [(5, 0)]
|
|
703
|
-
|
|
704
|
-
def check_variable_ids(self):
|
|
705
|
-
for button in self:
|
|
706
|
-
if len(button.variable_ids) > 1:
|
|
707
|
-
raise ValidationError(_("Buttons may only contain one placeholder."))
|
|
708
|
-
if button.variable_ids and button.url_type != "dynamic":
|
|
709
|
-
raise ValidationError(_("Only dynamic urls may have a placeholder."))
|
|
710
|
-
elif button.url_type == "dynamic" and not button.variable_ids:
|
|
711
|
-
raise ValidationError(_("All dynamic urls must have a placeholder."))
|
|
712
|
-
if button.variable_ids.name != "{{1}}":
|
|
713
|
-
raise ValidationError(
|
|
714
|
-
_("The placeholder for a button can only be {{1}}.")
|
|
715
|
-
)
|
|
@@ -2,7 +2,3 @@ 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
3
|
access_mail_whatsapp_template_group_system,mail_whatsapp_template_group_system,model_mail_whatsapp_template,base.group_system,1,1,1,1
|
|
4
4
|
access_mail_whatsapp_template_group_user,mail_whatsapp_template_group_user,model_mail_whatsapp_template,base.group_user,1,0,0,0
|
|
5
|
-
access_mail_whatsapp_template_button_group_system,access_mail_whatsapp_template_button_group_system,model_mail_whatsapp_template_button,base.group_system,1,1,1,1
|
|
6
|
-
access_mail_whatsapp_template_button_group_user,access_mail_whatsapp_template_button_group_user,model_mail_whatsapp_template_button,base.group_user,1,0,0,0
|
|
7
|
-
access_mail_whatsapp_template_variable_group_system,access_mail_whatsapp_template_variable_group_system,model_mail_whatsapp_template_variable,base.group_system,1,1,1,1
|
|
8
|
-
access_mail_whatsapp_template_variable_group_user,access_mail_whatsapp_template_variable_group_user,model_mail_whatsapp_template_variable,base.group_user,1,0,0,0
|
|
@@ -372,9 +372,9 @@ ul.auto-toc {
|
|
|
372
372
|
!! This file is generated by oca-gen-addon-readme !!
|
|
373
373
|
!! changes will be overwritten. !!
|
|
374
374
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
375
|
-
!! source digest: sha256:
|
|
375
|
+
!! source digest: sha256:0c5051305d7d04fb4c093d04523e165482783b281bc5c547fac9cf16c15c6ee3
|
|
376
376
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
|
|
377
|
-
<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/license-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/social/tree/
|
|
377
|
+
<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/license-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/social/tree/18.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-18-0/social-18-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=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
|
|
378
378
|
<p>This module allows to respond whatsapp chats.</p>
|
|
379
379
|
<p>This way, a group of users can respond customers or any other set of
|
|
380
380
|
partners in an integrated way.</p>
|
|
@@ -463,7 +463,7 @@ as validation Key</li>
|
|
|
463
463
|
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/social/issues">GitHub Issues</a>.
|
|
464
464
|
In case of trouble, please check there if your issue has already been reported.
|
|
465
465
|
If you spotted it first, help us to smash it by providing a detailed and welcomed
|
|
466
|
-
<a class="reference external" href="https://github.com/OCA/social/issues/new?body=module:%20mail_gateway_whatsapp%0Aversion:%
|
|
466
|
+
<a class="reference external" href="https://github.com/OCA/social/issues/new?body=module:%20mail_gateway_whatsapp%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
|
|
467
467
|
<p>Do not contact contributors directly about support or help with technical issues.</p>
|
|
468
468
|
</div>
|
|
469
469
|
<div class="section" id="credits">
|
|
@@ -505,7 +505,7 @@ If you spotted it first, help us to smash it by providing a detailed and welcome
|
|
|
505
505
|
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
|
|
506
506
|
mission is to support the collaborative development of Odoo features and
|
|
507
507
|
promote its widespread use.</p>
|
|
508
|
-
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/social/tree/
|
|
508
|
+
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/social/tree/18.0/mail_gateway_whatsapp">OCA/social</a> project on GitHub.</p>
|
|
509
509
|
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
|
|
510
510
|
</div>
|
|
511
511
|
</div>
|